/** @license 2021 Neil Edelman, distributed under the terms of the [MIT License](https://opensource.org/licenses/MIT). @abstract Source , depends on , and ; examples . @subtitle Stable pool ![Example of Pool](../web/pool.png) pool> is a memory pool that stores type>. Pointers to valid items in the pool are stable, but not generally in any order. When removal is ongoing and uniformly sampled while reaching a steady-state size, it will eventually settle in one contiguous region. @param[POOL_NAME, POOL_TYPE] `

` that satisfies `C` naming conventions when mangled and a valid tag type, type>, associated therewith; required. `` is private, whose names are prefixed in a manner to avoid collisions. @param[POOL_CHUNK_MIN_CAPACITY] Default is 8; optional number in `[2, (SIZE_MAX - sizeof pool_chunk) / sizeof type]` that the capacity can not go below. @param[POOL_EXPECT_TRAIT] Do not un-define certain variables for subsequent inclusion in a trait. @param[POOL_TO_STRING_NAME, POOL_TO_STRING] To string trait contained in ; `` that satisfies `C` naming conventions when mangled and function implementing to_string_fn>. There can be multiple to string traits, but only one can omit `POOL_TO_STRING_NAME`. This container is only partially iterable: the values are only the first chunk, so this is not very useful except for debugging. @depend [array](https://github.com/neil-edelman/array) @depend [heap](https://github.com/neil-edelman/heap) @std C89 */ #if !defined(POOL_NAME) || !defined(POOL_TYPE) #error Name POOL_NAME undefined or tag type POOL_TYPE undefined. #endif #if defined(POOL_TO_STRING_NAME) || defined(POOL_TO_STRING) #define POOL_TO_STRING_TRAIT 1 #else #define POOL_TO_STRING_TRAIT 0 #endif #define POOL_TRAITS POOL_TO_STRING_TRAIT #if POOL_TRAITS > 1 #error Only one trait per include is allowed; use POOL_EXPECT_TRAIT. #endif #if defined(POOL_TO_STRING_NAME) && !defined(POOL_TO_STRING) #error POOL_TO_STRING_NAME requires POOL_TO_STRING. #endif #ifndef POOL_H /* */ #if POOL_TRAITS == 0 /* */ #if POOL_CHUNK_MIN_CAPACITY < 2 #error Pool chunk capacity error. #endif /** A valid tag type set by `POOL_TYPE`. */ typedef POOL_TYPE PP_(type); /* Size and chunk, which goes into a sorted array. */ struct PP_(slot) { size_t size; PP_(type) *chunk; }; #define ARRAY_NAME PP_(slot) #define ARRAY_TYPE struct PP_(slot) #include "array.h" /** Consists of a map of several chunks of increasing size and a free-heap. Zeroed data is a valid state. To instantiate to an idle state, see pool>, `POOL_IDLE`, `{0}` (`C99`,) or being `static`. ![States.](../web/states.png) */ struct P_(pool) { struct PP_(slot_array) slots; struct poolfree_heap free0; /* Free-list in chunk-zero. */ size_t capacity0; /* Capacity of chunk-zero. */ }; /** @return Index of sorted chunk that is higher than `x` in `slots`, but treating zero as special. @order \O(\log `chunks`) */ static size_t PP_(upper)(const struct PP_(slot_array) *const slots, const PP_(type) *const x) { const struct PP_(slot) *const base = slots->data; size_t n, b0, b1; assert(slots && x); if(!(n = slots->size)) return 0; assert(base); if(!--n) return 1; /* The last one is a special case: it doesn't have an upper bound. */ for(b0 = 1, --n; n; n /= 2) { b1 = b0 + n / 2; if(x < base[b1].chunk) { continue; } else if(base[b1 + 1].chunk <= x) { b0 = b1 + 1; n--; continue; } else { return b1 + 1; } } return b0 + (x >= base[slots->size - 1].chunk); } /** Which chunk is `x` in `pool`? @order \O(\log `chunks`), \O(\log \log `size`)? */ static size_t PP_(chunk_idx)(const struct P_(pool) *const pool, const PP_(type) *const x) { struct PP_(slot) *const base = pool->slots.data; size_t up; assert(pool && pool->slots.size && base && x); /* One chunk, assume it's in that chunk; first chunk is `capacity0`. */ if(pool->slots.size <= 1 || (x >= base[0].chunk && x < base[0].chunk + pool->capacity0)) return assert(x >= base[0].chunk && x < base[0].chunk + pool->capacity0), 0; up = PP_(upper)(&pool->slots, x); return assert(up), up - 1; } /** Makes sure there are space for `n` further items in `pool`. @return Success. */ static int PP_(buffer)(struct P_(pool) *const pool, const size_t n) { const size_t min_size = POOL_CHUNK_MIN_CAPACITY, max_size = (size_t)-1 / sizeof(PP_(type)); struct PP_(slot) *base = pool->slots.data, *slot; PP_(type) *chunk; size_t c, insert; int is_recycled = 0; assert(pool && min_size <= max_size && pool->capacity0 <= max_size && (!pool->slots.size && !pool->free0.a.size /* !chunks[0] -> !free0 */ || pool->slots.size && base && base[0].size <= pool->capacity0 && (!pool->free0.a.size || pool->free0.a.size < base[0].size && pool->free0.a.data[0] < base[0].size))); /* Ensure space for new slot. */ if(!n || pool->slots.size && n <= pool->capacity0 - base[0].size + pool->free0.a.size) return 1; /* Already enough. */ if(max_size < n) return errno = ERANGE, 1; /* Request unsatisfiable. */ if(!PP_(slot_array_buffer)(&pool->slots, 1)) return 0; base = pool->slots.data; /* It may have moved! */ /* Figure out the capacity of the next chunk. */ c = pool->capacity0; if(pool->slots.size && base[0].size) { /* ~Golden ratio. */ size_t c1 = c + (c >> 1) + (c >> 3); c = (c1 < c || c1 > max_size) ? max_size : c1; } if(c < min_size) c = min_size; if(c < n) c = n; /* Allocate it. */ if(pool->slots.size && !base[0].size) is_recycled = 1, chunk = realloc(base[0].chunk, c * sizeof *chunk); else chunk = malloc(c * sizeof *chunk); if(!chunk) { if(!errno) errno = ERANGE; return 0; } pool->capacity0 = c; /* We only need to store the capacity of chunk 0. */ if(is_recycled) return base[0].size = 0, base[0].chunk = chunk, 1; /* Evict chunk 0. */ if(!pool->slots.size) insert = 0; else insert = PP_(upper)(&pool->slots, base[0].chunk); assert(insert <= pool->slots.size); slot = PP_(slot_array_insert)(&pool->slots, 1, insert); assert(slot); /* Made space for it before. */ slot->chunk = base[0].chunk, slot->size = base[0].size; base[0].chunk = chunk, base[0].size = 0; return 1; } /** Either `data` in `pool` is in a secondary chunk, in which case it decrements the size, or it's the zero-chunk, where it gets added to the free-heap. @return Success. It may fail due to a free-heap memory allocation error. @order Amortized \O(\log \log `items`) @throws[realloc] */ static int PP_(remove)(struct P_(pool) *const pool, const PP_(type) *const data) { size_t c = PP_(chunk_idx)(pool, data); struct PP_(slot) *slot = pool->slots.data + c; assert(pool && pool->slots.size && data); if(!c) { /* It's in the zero-slot, we need to deal with the free-heap. */ const size_t idx = (size_t)(data - slot->chunk); assert(pool->capacity0 && slot->size <= pool->capacity0 && idx < slot->size); if(idx + 1 == slot->size) { /* Keep shrinking going while item on the free-heap are exposed. */ while(--slot->size && !poolfree_heap_is_empty(&pool->free0)) { const size_t free = poolfree_heap_peek(&pool->free0); if(free < slot->size - 1) break; assert(free == slot->size - 1); poolfree_heap_pop(&pool->free0); } } else if(!poolfree_heap_add(&pool->free0, idx)) return 0; } else if(assert(slot->size), !--slot->size) { PP_(type) *const chunk = slot->chunk; PP_(slot_array_remove)(&pool->slots, pool->slots.data + c); free(chunk); } return 1; } /** Initializes `pool` to idle. @order \Theta(1) @allow */ static void P_(pool)(struct P_(pool) *const pool) { assert(pool), PP_(slot_array)(&pool->slots), poolfree_heap(&pool->free0), pool->capacity0 = 0; } /** Destroys `pool` and returns it to idle. @order \O(\log `data`) @allow */ static void P_(pool_)(struct P_(pool) *const pool) { struct PP_(slot) *s, *s_end; assert(pool); for(s = pool->slots.data, s_end = s + pool->slots.size; s < s_end; s++) assert(s->chunk), free(s->chunk); PP_(slot_array_)(&pool->slots); poolfree_heap_(&pool->free0); P_(pool)(pool); } /** Ensure capacity of at least `n` further items in `pool`. Pre-sizing is better for contiguous blocks, but takes up that memory. @return Success. @throws[ERANGE, malloc] @allow */ static int P_(pool_buffer)(struct P_(pool) *const pool, const size_t n) { return assert(pool), PP_(buffer)(pool, n); } /** This pointer is constant until it gets pool_remove>. @return A pointer to a new uninitialized element from `pool`. @throws[ERANGE, malloc] @order amortised O(1) @allow */ static PP_(type) *P_(pool_new)(struct P_(pool) *const pool) { struct PP_(slot) *slot0; assert(pool); if(!PP_(buffer)(pool, 1)) return 0; assert(pool->slots.size && (pool->free0.a.size || pool->slots.data[0].size < pool->capacity0)); if(!poolfree_heap_is_empty(&pool->free0)) { /* Cheating: we prefer the minimum index from a max-heap, but it doesn't really matter, so take the one off the array used for heap. */ size_t *free; free = heap_poolfree_node_array_pop(&pool->free0.a); return assert(free), pool->slots.data[0].chunk + *free; } /* The free-heap is empty; guaranteed by buffer>. */ slot0 = pool->slots.data + 0; assert(slot0 && slot0->size < pool->capacity0); return slot0->chunk + slot0->size++; } /** Deletes `data` from `pool`. Do not remove data that is not in `pool`. @return Success. @order \O(\log \log `items`) @allow */ static int P_(pool_remove)(struct P_(pool) *const pool, PP_(type) *const data) { return PP_(remove)(pool, data); } /** Removes all from `pool`, but keeps it's active state, only freeing the smaller blocks. @order \O(\log `items`) @allow */ static void P_(pool_clear)(struct P_(pool) *const pool) { struct PP_(slot) *s, *s_end; assert(pool); if(!pool->slots.size) { assert(!pool->free0.a.size); return; } for(s = pool->slots.data + 1, s_end = s - 1 + pool->slots.size; s < s_end; s++) assert(s->chunk && s->size), free(s->chunk); pool->slots.data[0].size = 0; pool->slots.size = 1; poolfree_heap_clear(&pool->free0); } /* */ #ifdef POOL_TEST /* */ /* */ #define TO_STRING POOL_TO_STRING #include "to_string.h" /** \include */ #ifdef POOL_TEST /* */ #undef SZ_ #undef POOL_TO_STRING #ifdef POOL_TO_STRING_NAME #undef POOL_TO_STRING_NAME #endif #endif /* traits --> */ #ifdef POOL_EXPECT_TRAIT /* */ #endif /* !trait --> */ #undef POOL_TO_STRING_TRAIT #undef POOL_TRAITS