From e1f510adada5dbb219b359ff83244e9abdae55e9 Mon Sep 17 00:00:00 2001 From: nircoe Date: Sun, 27 Sep 2026 17:28:46 +0300 Subject: [PATCH 1/2] [Core]: Fix entity generation overflow --- src/gamecoe/entity/entities.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gamecoe/entity/entities.cpp b/src/gamecoe/entity/entities.cpp index 0c162ac..857bd17 100644 --- a/src/gamecoe/entity/entities.cpp +++ b/src/gamecoe/entity/entities.cpp @@ -33,9 +33,6 @@ namespace gamecoe id = m_recycle_ids.back(); m_recycle_ids.pop_back(); generation = m_generations[id]; - GAMECOE_ASSERT_GUARD(generation <= entity::MAX_GENERATIONS, - "entities::create(): recycled entity generation exceeds maximum", - entity::invalid()); m_self_active[id] = true; } @@ -85,7 +82,9 @@ namespace gamecoe to_destroy.insert(to_destroy.end(), kids->handles.begin(), kids->handles.end()); m_generations[current.id()]++; - m_recycle_ids.push_back(current.id()); + bool exhausted = m_generations[current.id()] > entity::MAX_GENERATIONS; + GAMECOE_ASSERT_LOG(!exhausted, "entities::destroy(): id's generation reached the maximum, permanently retiring this id"); + if (!exhausted) m_recycle_ids.push_back(current.id()); for (auto &pool : m_pools) if (pool) pool->remove(current); } From 2f3d41e797f675ec7d0588e88a9c3c524d90eeb7 Mon Sep 17 00:00:00 2001 From: nircoe Date: Mon, 28 Sep 2026 18:54:55 +0300 Subject: [PATCH 2/2] Add test for recycle generation overflow guard --- tests/entity/entities_tests.cpp | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/entity/entities_tests.cpp b/tests/entity/entities_tests.cpp index 2a3d097..5da010c 100644 --- a/tests/entity/entities_tests.cpp +++ b/tests/entity/entities_tests.cpp @@ -119,6 +119,48 @@ TEST_F(EntitiesTests, EntityLifecycle) } } +//============================================================================== +// Recycle Generation Overflow (destroy()-side guard) +//============================================================================== + +#ifndef NDEBUG +TEST_F(EntitiesTests, RecycleGenerationOverflowIsGuarded) +{ + mgr.clear(); + entity e = mgr.create(); + + // Drive this one id's generation up to MAX_GENERATIONS by repeatedly recycling it: as the + // only entity in the recycle stack, each destroy()/create() pair recycles the same id (LIFO + // via m_recycle_ids.back()/pop_back()). + for (std::uint16_t gen = 0; gen < entity::MAX_GENERATIONS; ++gen) + { + mgr.destroy(e); + e = mgr.create(); + } + + EXPECT_DEATH(mgr.destroy(e), "id's generation reached the maximum"); +} +#else +TEST_F(EntitiesTests, RecycleGenerationOverflowRetiresId) +{ + mgr.clear(); + entity e = mgr.create(); + std::uint32_t id = e.id(); + + for (std::uint16_t gen = 0; gen < entity::MAX_GENERATIONS; ++gen) + { + mgr.destroy(e); + e = mgr.create(); + } + + mgr.destroy(e); + + // Id is permanently retired - never handed out again by a later create() call. + for (int i = 0; i < 100; ++i) + EXPECT_NE(mgr.create().id(), id); +} +#endif + //============================================================================== // Move Semantics //==============================================================================