161 lines
7.0 KiB
C
161 lines
7.0 KiB
C
|
|
/**
|
||
|
|
* @file collision_arena.h
|
||
|
|
* @brief The bump allocator libccd allocates from, so that libakgl does not.
|
||
|
|
*
|
||
|
|
* libakgl does not call `malloc` at runtime. Every object comes from a fixed
|
||
|
|
* array declared in `akgl/heap.h`, and that is a promise the manual makes in
|
||
|
|
* seven places and `AGENTS.md` makes as a rule. libccd's narrowphase does not
|
||
|
|
* know that: its EPA path builds an expanding polytope out of `realloc` and
|
||
|
|
* `free`, and `ccdGJKPenetration` documents a `-2` return for the case where
|
||
|
|
* that fails.
|
||
|
|
*
|
||
|
|
* Rather than compile a hand-picked subset of libccd's sources -- which puts a
|
||
|
|
* file list in this repository that really lives upstream, and rots silently on
|
||
|
|
* the next bump -- the whole library is compiled and pointed at this arena.
|
||
|
|
* `CMakeLists.txt` redirects `__CCD_ALLOC_MEMORY`, and `src/ccd_arena_shim.h`
|
||
|
|
* redirects `free`, on libccd's translation units only.
|
||
|
|
*
|
||
|
|
* @section arena_lifetime The lifetime is one narrowphase call
|
||
|
|
*
|
||
|
|
* `akgl_collision_test` resets the arena on entry, so every allocation lives
|
||
|
|
* exactly as long as the query that made it. libccd frees its polytope before
|
||
|
|
* returning in any case, so nothing needs to survive, and that is what lets
|
||
|
|
* `akgl_ccd_arena_free` be a no-op and the allocator be a pointer bump. There
|
||
|
|
* is no free list, no coalescing and no fragmentation, because there is no
|
||
|
|
* reuse within a call.
|
||
|
|
*
|
||
|
|
* @section arena_exhaustion Exhaustion is reported, not fatal
|
||
|
|
*
|
||
|
|
* A refused allocation returns `NULL`, which libccd already handles -- it
|
||
|
|
* unwinds and returns `-2`, which `akgl_collision_test` turns into
|
||
|
|
* #AKGL_ERR_COLLISION naming the high-water mark. A too-small arena is a loud
|
||
|
|
* failure with a number in it, not a crash and not a silently missed collision.
|
||
|
|
*
|
||
|
|
* @warning **Not thread safe.** There is one arena and it has no lock. libakgl
|
||
|
|
* serialises its frame behind `akgl_game.statelock`; a caller running
|
||
|
|
* narrowphase queries from two threads needs a different design here,
|
||
|
|
* not a mutex bolted on.
|
||
|
|
*
|
||
|
|
* @note These are exported so the test suite can check that libccd really is
|
||
|
|
* allocating from here, and so a benchmark can record the high-water
|
||
|
|
* mark. A game has no reason to call them.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#ifndef _AKGL_COLLISION_ARENA_H_
|
||
|
|
#define _AKGL_COLLISION_ARENA_H_
|
||
|
|
|
||
|
|
#include <stdbool.h>
|
||
|
|
#include <stddef.h>
|
||
|
|
#include <stdint.h>
|
||
|
|
|
||
|
|
#include <akerror.h>
|
||
|
|
|
||
|
|
#include <akgl/types.h>
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Bytes of static storage the arena hands out.
|
||
|
|
*
|
||
|
|
* Sized by measurement rather than derivation: the benchmark records the
|
||
|
|
* high-water mark and this is set above the worst observed case. It bounds
|
||
|
|
* memory the same way `ccd_t::max_iterations` bounds time, and both must be
|
||
|
|
* bounded -- libccd's own default for the latter is `(unsigned long)-1`.
|
||
|
|
*/
|
||
|
|
#ifndef AKGL_CCD_ARENA_BYTES
|
||
|
|
#define AKGL_CCD_ARENA_BYTES (64 * 1024)
|
||
|
|
#endif
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Allocate or grow a block, in the shape `realloc(3)` has.
|
||
|
|
*
|
||
|
|
* This is what `__CCD_ALLOC_MEMORY` expands to inside libccd. A `NULL` @p ptr
|
||
|
|
* allocates; a non-`NULL` one allocates a new block and copies the old contents
|
||
|
|
* across, because a bump allocator cannot grow in place unless the block is the
|
||
|
|
* most recent one, and it is not worth a special case for a path that runs at
|
||
|
|
* most a few times per query.
|
||
|
|
*
|
||
|
|
* @param ptr Block to grow, or `NULL` to allocate a new one.
|
||
|
|
* @param size Bytes wanted. A @p size of 0 releases nothing and returns `NULL`,
|
||
|
|
* matching what libccd does with the result.
|
||
|
|
* @return A pointer into the arena, or `NULL` when the arena is full.
|
||
|
|
*/
|
||
|
|
void *akgl_ccd_arena_realloc(void *ptr, size_t size);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Release a block. A no-op, because the whole arena is released at once.
|
||
|
|
*
|
||
|
|
* @param ptr Block to release. `NULL` is accepted, as `free(3)` accepts it.
|
||
|
|
*/
|
||
|
|
void akgl_ccd_arena_free(void *ptr);
|
||
|
|
|
||
|
|
/** @brief Drop everything. Called at the top of each narrowphase query. */
|
||
|
|
void akgl_ccd_arena_reset(void);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Bytes currently handed out.
|
||
|
|
*
|
||
|
|
* Zero after `akgl_ccd_arena_reset`, and non-zero after a narrowphase query
|
||
|
|
* that allocated -- releasing a block does not lower it, because
|
||
|
|
* akgl_ccd_arena_free is a no-op and the whole arena is released at once.
|
||
|
|
*
|
||
|
|
* Non-zero after a query is what proves libccd is allocating from here rather
|
||
|
|
* than from the C library, which is worth checking rather than assuming: the
|
||
|
|
* redirect lived in CMake's COMPILE_DEFINITIONS at first, where a function-like
|
||
|
|
* macro cannot survive, and it was dropped without a diagnostic.
|
||
|
|
*/
|
||
|
|
size_t akgl_ccd_arena_used(void);
|
||
|
|
|
||
|
|
/** @brief The most bytes ever handed out at once, since the process started. */
|
||
|
|
size_t akgl_ccd_arena_highwater(void);
|
||
|
|
|
||
|
|
/** @brief How many allocations have been refused for want of room. */
|
||
|
|
uint32_t akgl_ccd_arena_exhausted(void);
|
||
|
|
|
||
|
|
/*
|
||
|
|
* The following is part of the internal API. It is exposed so the test suite
|
||
|
|
* can reach it and is not meant to be called by a game.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Run one narrowphase query through libccd's allocating path.
|
||
|
|
*
|
||
|
|
* Two boxes of half-extent 1 are placed @p separation apart on x and tested
|
||
|
|
* with `ccdGJKPenetration` -- the EPA path, chosen because EPA is the half of
|
||
|
|
* libccd that allocates and therefore the half that exercises the arena. A
|
||
|
|
* narrowphase on the frame path prefers MPR, which allocates nothing.
|
||
|
|
*
|
||
|
|
* This exists so a test can reach two things nothing else can. That a real
|
||
|
|
* libccd query allocates from the arena rather than the C library, which
|
||
|
|
* akgl_ccd_arena_used() shows. And that no `free` inside libccd escapes to the
|
||
|
|
* C library, which is shown by the process surviving: the real `free(3)` given
|
||
|
|
* a pointer it never issued is undefined behaviour, and glibc aborts on it.
|
||
|
|
*
|
||
|
|
* @param separation Distance between the two box centres on x. Below 2.0 they
|
||
|
|
* overlap; above it they do not.
|
||
|
|
* @param hit Receives whether the boxes intersect. Required.
|
||
|
|
* @param depth Receives the penetration depth, or 0 when they do not
|
||
|
|
* intersect. Required.
|
||
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||
|
|
* @throws AKERR_NULLPOINTER If @p hit or @p depth is `NULL`.
|
||
|
|
* @throws AKGL_ERR_COLLISION If the arena ran out of room. The message carries
|
||
|
|
* the high-water mark and the current ceiling.
|
||
|
|
*/
|
||
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_collision_arena_selftest(float32_t separation, bool *hit, float32_t *depth);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @brief Narrow the arena, so a test can drive a real query into exhaustion.
|
||
|
|
*
|
||
|
|
* The interesting failure is not that akgl_ccd_arena_realloc returns `NULL` --
|
||
|
|
* that is one comparison. It is what happens afterwards: libccd has to notice,
|
||
|
|
* unwind its half-built polytope, and return `-2`, and the caller has to turn
|
||
|
|
* that into #AKGL_ERR_COLLISION rather than reporting a missed collision or
|
||
|
|
* dereferencing the `NULL`. Reaching that path any other way means rebuilding
|
||
|
|
* the library with a smaller #AKGL_CCD_ARENA_BYTES, which CI cannot do and a
|
||
|
|
* reader cannot repeat.
|
||
|
|
*
|
||
|
|
* @param limit Usable bytes. 0, or anything above #AKGL_CCD_ARENA_BYTES,
|
||
|
|
* restores the full arena.
|
||
|
|
*/
|
||
|
|
void akgl_ccd_arena_set_limit(size_t limit);
|
||
|
|
|
||
|
|
#endif
|