diff --git a/src/guf_id_pool.h b/src/guf_id_pool.h index 5a41d06..b6c76b3 100644 --- a/src/guf_id_pool.h +++ b/src/guf_id_pool.h @@ -8,22 +8,76 @@ #define GUF_ID_POOL_H #include "guf_common.h" #include "guf_assert.h" + + #define GUF_ID_i32_NULL (-1) + #define GUF_ID_i16_NULL (-1) + #define GUF_ID_i8_NULL (-1) + + #define GUF_ID_u32_NULL (UINT32_MAX) + #define GUF_ID_u16_NULL (UINT16_MAX) + #define GUF_ID_u8_NULL (UINT8_MAX) #endif -// cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c (last-retrieved 2025-03-11) +/* + ID-pool which allows for allocating and freeing unique IDs (useful when implementing object pools): + cf. https://github.com/erincatto/box2d/blob/main/src/id_pool.c (last-retrieved 2025-03-17) +*/ // // test beg #define GUF_ID_POOL_IMPL_STATIC -#define GUF_ID_POOL_T uint32_t -#define GUF_ID_POOL_NAME guf_idpool_u32 +#define GUF_ID_POOL_i32 // // test end -#ifndef GUF_ID_POOL_T - #error "Must pass GUF_ID_POOL_T" -#endif - -#ifndef GUF_ID_POOL_NAME - #error "Must pass GUF_ID_POOL_NAME" +#if defined(GUF_ID_POOL_i32) + #undef GUF_ID_POOL_i32 + #define GUF_ID_POOL_T int32_t + #define GUF_ID_POOL_T_MAX INT32_MAX + #define GUF_ID_POOL_NULL GUF_ID_i32_NULL + #ifndef GUF_ID_POOL_NAME + #define GUF_ID_POOL_NAME guf_idpool_i32 + #endif +#elif defined(GUF_ID_POOL_i16) + #undef GUF_ID_POOL_i16 + #define GUF_ID_POOL_T int16_t + #define GUF_ID_POOL_T_MAX INT16_MAX + #define GUF_ID_POOL_NULL GUF_ID_i16_NULL + #ifndef GUF_ID_POOL_NAME + #define GUF_ID_POOL_NAME guf_idpool_i16 + #endif +#elif defined(GUF_ID_POOL_i8) + #undef GUF_ID_POOL_i8 + #define GUF_ID_POOL_T int8_t + #define GUF_ID_POOL_T_MAX INT8_MAX + #define GUF_ID_POOL_NULL GUF_ID_i8_NULL + #ifndef GUF_ID_POOL_NAME + #define GUF_ID_POOL_NAME guf_idpool_i8 + #endif +#elif defined(GUF_ID_POOL_u32) + #undef GUF_ID_POOL_u32 + #define GUF_ID_POOL_T uint32_t + #define GUF_ID_POOL_T_MAX (UINT32_MAX - 1) + #define GUF_ID_POOL_NULL GUF_ID_u32_NULL + #ifndef GUF_ID_POOL_NAME + #define GUF_ID_POOL_NAME guf_idpool_u32 + #endif +#elif defined(GUF_ID_POOL_u16) + #undef GUF_ID_POOL_u16 + #define GUF_ID_POOL_T uint16_t + #define GUF_ID_POOL_T_MAX (UINT16_MAX - 1) + #define GUF_ID_POOL_NULL GUF_ID_u16_NULL + #ifndef GUF_ID_POOL_NAME + #define GUF_ID_POOL_NAME guf_idpool_u16 + #endif +#elif defined(GUF_ID_POOL_u8) + #undef GUF_ID_POOL_u8 + #define GUF_ID_POOL_T uint8_t + #define GUF_ID_POOL_T_MAX (UINT8_MAX - 1) + #define GUF_ID_POOL_NULL GUF_ID_u8_NULL + #ifndef GUF_ID_POOL_NAME + #define GUF_ID_POOL_NAME guf_idpool_u8 + #endif +#else + #error "No GUF_ID_POOL_i defined" #endif #define GUF_ID_POOL_DBUF GUF_CAT(GUF_ID_POOL_NAME, _id_dbuf) @@ -34,11 +88,30 @@ #define GUF_DBUF_ONLY_TYPES #include "guf_dbuf.h" -#ifndef GUF_ID_POOL_IMPL +#ifdef GUF_ID_POOL_ONLY_TYPES + #undef GUF_ID_POOL_ONLY_TYPES typedef struct GUF_ID_POOL_NAME { GUF_ID_POOL_DBUF free_id_dbuf; GUF_ID_POOL_T next_id; } GUF_ID_POOL_NAME; +#else + +#ifndef GUF_ID_POOL_IMPL + typedef struct GUF_ID_POOL_NAME { + GUF_ID_POOL_DBUF free_id_dbuf; // Stores all the freed ids to reuse. + GUF_ID_POOL_T next_id; // The next-id to assign in case we don't have any freed ids to reuse. + } GUF_ID_POOL_NAME; + + GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err); + GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator); + + void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx); + + GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err); + GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool); + + void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err); + void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id); #endif #if defined(GUF_ID_POOL_IMPL) || defined(GUF_ID_POOL_IMPL_STATIC) @@ -50,6 +123,16 @@ #define GUF_DBUF_IMPL_STATIC #include "guf_dbuf.h" +static bool GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(const GUF_ID_POOL_NAME *pool) +{ + if (!pool) { + return false; + } + bool valid_next_id = (pool->next_id == GUF_ID_POOL_NULL) || (pool->next_id >= 0 && pool->next_id <= GUF_ID_POOL_T_MAX); + bool valid_bufsize = ((pool->free_id_dbuf.size <= (ptrdiff_t)pool->next_id) || (pool->next_id == GUF_ID_POOL_NULL)) && (size_t)pool->free_id_dbuf.size <= (size_t)GUF_ID_POOL_T_MAX + 1; + return valid_next_id && valid_bufsize && GUF_CAT(GUF_ID_POOL_DBUF, _valid)(&pool->free_id_dbuf) && pool->free_id_dbuf.size >= 0; +} + GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator, guf_err *err) { if (!pool) { @@ -63,6 +146,7 @@ GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, p return NULL; } + pool->free_id_dbuf = {0}; GUF_CAT(GUF_ID_POOL_DBUF, _try_init)(&pool->free_id_dbuf, initial_cap, allocator, err); if (err && *err != GUF_ERR_NONE) { guf_err_set_or_panic(err, *err, "in guf_idpool_try_init: failed to allocate dbuf"); @@ -75,50 +159,103 @@ GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _try_init)(GUF_ID_POOL_NAME *pool, p return pool; } -GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool) +GUF_ID_POOL_NAME *GUF_CAT(GUF_ID_POOL_NAME, _init)(GUF_ID_POOL_NAME *pool, ptrdiff_t initial_cap, guf_allocator *allocator) +{ + return GUF_CAT(GUF_ID_POOL_NAME, _try_init)(pool, initial_cap, allocator, NULL); +} + +void GUF_CAT(GUF_ID_POOL_NAME, _free)(GUF_ID_POOL_NAME *pool, void *ctx) +{ + (void)ctx; + if (!pool) { + return; + } + GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool)); + + GUF_CAT(GUF_ID_POOL_DBUF, _free)(&pool->free_id_dbuf, NULL); + pool->free_id_dbuf = {0}; + pool->next_id = 0; +} + +GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(GUF_ID_POOL_NAME *pool, guf_err *err) { GUF_ASSERT(pool); - GUF_ASSERT(pool->free_id_dbuf.size >= 0); + GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool)); if (pool->free_id_dbuf.size > 0) { GUF_ID_POOL_T id = *GUF_CAT(GUF_ID_POOL_DBUF, _back)(&pool->free_id_dbuf); GUF_CAT(GUF_ID_POOL_DBUF, _pop)(&pool->free_id_dbuf); + GUF_ASSERT(id != GUF_ID_POOL_NULL && id <= GUF_ID_POOL_T_MAX); + guf_err_set_if_not_null(err, GUF_ERR_NONE); return id; + } + + GUF_ID_POOL_T id = pool->next_id; + if (id == GUF_ID_POOL_T_MAX || id == GUF_ID_POOL_NULL) { + pool->next_id = GUF_ID_POOL_NULL; } else { - GUF_ID_POOL_T id = pool->next_id; - pool->next_id += 1; // TODO: handle overflow (need a guf_id_pool_t_max) - return id; + pool->next_id += 1; } + if (id == GUF_ID_POOL_NULL) { + guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, "in guf_idpool_try_alloc_id: Out of ids."); + } else { + guf_err_set_if_not_null(err, GUF_ERR_NONE); + } + return id; +} + +GUF_ID_POOL_T GUF_CAT(GUF_ID_POOL_NAME, _alloc_id)(GUF_ID_POOL_NAME *pool) +{ + return GUF_CAT(GUF_ID_POOL_NAME, _try_alloc_id)(pool, NULL); } void GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id, guf_err *err) { GUF_ASSERT(pool); - GUF_ASSERT(pool->next_id > 0); + GUF_ASSERT(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool)); - if (id < 0 || id >= pool->next_id) { + if (id == GUF_ID_POOL_NULL) { // ID is NULL. + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, "in guf_idpool_try_free_id: id is NULL"); + return; + } else if (id < 0 || id >= pool->next_id) { // ID is beyond the allocated range. guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, "in guf_idpool_try_free_id: id < 0 or id >= pool->next_id"); return; + } else if (pool->next_id == 0) { // There haven't been any IDs allocated yet. + GUF_ASSERT(pool->free_id_dbuf.size == 0); + guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, "in guf_idpool_try_free_id: pool->next_id == 0"); + return; } - if (id == pool->next_id - 1) { + // NOTE: https://github.com/erincatto/box2d/issues/893 (last-retrieved 2025-03-17) + if (id == pool->next_id - 1) { // Optimisation in case we remove the largest allocated id so far pool->next_id -= 1; + guf_err_set_if_not_null(err, GUF_ERR_NONE); return; } GUF_CAT(GUF_ID_POOL_DBUF, _try_push_val)(&pool->free_id_dbuf, id, err); - if (err && *err != GUF_ERR_NONE) { guf_err_set_or_panic(err, *err, "in guf_idpool_try_free_id: failed to push id"); - return; } else { guf_err_set_if_not_null(err, GUF_ERR_NONE); } } +void GUF_CAT(GUF_ID_POOL_NAME, _free_id)(GUF_ID_POOL_NAME *pool, GUF_ID_POOL_T id) +{ + GUF_CAT(GUF_ID_POOL_NAME, _try_free_id)(pool, id, NULL); +} + #endif /* end impl */ +#endif /* end only-types */ + #undef GUF_ID_POOL_KWRDS #undef GUF_ID_POOL_IMPL #undef GUF_ID_POOL_IMPL_STATIC +#undef GUF_ID_POOL_T +#undef GUF_ID_POOL_T_MAX +#undef GUF_ID_POOL_NULL +#undef GUF_ID_POOL_NAME +