277 lines
9.5 KiB
C
Executable File
277 lines
9.5 KiB
C
Executable File
/*
|
|
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
|
|
|