/* is parametrized: yes */ #if defined(GUF_ID_POOL_IMPL_STATIC) #define GUF_ID_POOL_KWRDS static #else #define GUF_ID_POOL_KWRDS #endif #ifndef GUF_ID_POOL_H #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 (GUF_UINT32_MAX) #define GUF_ID_U16_NULL (GUF_UINT16_MAX) #define GUF_ID_U8_NULL (GUF_UINT8_MAX) #endif /* 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) */ // // debug beg // #define GUF_ID_POOL_IMPL_STATIC // #define GUF_ID_POOL_i32 // // debug end #if defined(GUF_ID_POOL_i32) #undef GUF_ID_POOL_i32 #define GUF_ID_POOL_T int_least32_t #define GUF_ID_POOL_T_MAX GUF_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 int_least16_t #define GUF_ID_POOL_T_MAX GUF_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 int_least8_t #define GUF_ID_POOL_T_MAX GUF_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 uint_least32_t #define GUF_ID_POOL_T_MAX (GUF_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 uint_least16_t #define GUF_ID_POOL_T_MAX (GUF_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 uint_least8_t #define GUF_ID_POOL_T_MAX (GUF_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 "Must define GUF_ID_POOL_i32, GUF_ID_POOL_i16, GUF_ID_POOL_i8, GUF_ID_POOL_u32, GUF_ID_POOL_u16, or GUF_ID_POOL_u8" #endif #define GUF_ID_POOL_DBUF GUF_CAT(GUF_ID_POOL_NAME, _id_dbuf) #define GUF_DBUF_NAME GUF_ID_POOL_DBUF #define GUF_T GUF_ID_POOL_T #define GUF_T_IS_INTEGRAL_TYPE #define GUF_DBUF_ONLY_TYPES #include "guf_dbuf.h" #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); GUF_ID_POOL_NAME ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool); // TODO: test #endif #if defined(GUF_ID_POOL_IMPL) || defined(GUF_ID_POOL_IMPL_STATIC) #define GUF_DBUF_NAME GUF_ID_POOL_DBUF #define GUF_T GUF_ID_POOL_T #define GUF_T_IS_INTEGRAL_TYPE #define GUF_DBUF_WITHOUT_TYPES #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 ptrdiff_t GUF_CAT(GUF_ID_POOL_NAME, _num_allocated)(const GUF_ID_POOL_NAME *pool) { GUF_ASSERT((ptrdiff_t)pool->next_id >= free_id_dbuf.size) return pool->next_id - free_id_dbuf.size; } 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) { guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: pool is NULL")); return NULL; } else if (!allocator || !allocator->alloc || !allocator->realloc || !allocator->free) { guf_err_set_or_panic(err, GUF_ERR_NULL_PTR, GUF_ERR_MSG("in guf_idpool_try_init: allocator is NULL")); return NULL; } else if (initial_cap < 0) { guf_err_set_or_panic(err, GUF_ERR_INVALID_ARG, GUF_ERR_MSG("in guf_idpool_try_init: initial_cap is < 0")); 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, GUF_ERR_MSG("in guf_idpool_try_init: failed to allocate dbuf")); return NULL; } pool->next_id = 0; guf_err_set_if_not_null(err, GUF_ERR_NONE); return 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(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 { pool->next_id += 1; } if (id == GUF_ID_POOL_NULL) { guf_err_set_or_panic(err, GUF_ERR_INT_OVERFLOW, GUF_ERR_MSG("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(GUF_CAT(GUF_ID_POOL_NAME, _is_valid)(pool)); if (id == GUF_ID_POOL_NULL) { // ID is NULL. guf_err_set_or_panic(err, GUF_ERR_IDX_RANGE, GUF_ERR_MSG("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, GUF_ERR_MSG("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, GUF_ERR_MSG("in guf_idpool_try_free_id: pool->next_id == 0")); return; } /* // NOTE: https://github.com/erincatto/box2d/issues/893 (last-retrieved 2025-04-09) 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, GUF_ERR_MSG("in guf_idpool_try_free_id: failed to push id")); } 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