Remove the corner helpers akgl_collide_rectangles no longer uses
akgl_rectangle_points, akgl_collide_point_rectangle, akgl_Point and akgl_RectanglePoints go. They were the intermediate form of an implementation that changed: akgl_collide_rectangles was eight corner-containment tests built on them, and it has been four span comparisons since the cross-case fix. Nothing outside tests/ called either function, and a point-in-rectangle test is four comparisons a caller can write without a struct conversion in front of them. akgl_collide_rectangles stays. It has two correct callers in the sidescroller asking a game-level overlap question -- a coin, a hazard, from an updatefunc -- where a bool is the whole answer and a proxy plus a narrowphase call would be computing a normal nothing reads. TODO.md records the split rather than leaving it to be rediscovered. Public API removal, so 194 exported akgl_ symbols against 196, and the manual's counts move with them. The perf suite loses its rectangle_points row; the all-pairs sweep stays as the control it is now labelled, and PERFORMANCE.md says what 0.8.0 measured against it -- 188.5 us for 32,640 pairs at 256 actors, where a whole step with collision attached is 54.1 us doing strictly more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
This commit is contained in:
26
tests/perf.c
26
tests/perf.c
@@ -758,14 +758,6 @@ static akerr_ErrorContext *bench_logic_frame(void)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Time the geometry helpers, including the all-pairs sweep a caller has to write.
|
||||
*
|
||||
* akgl_collide_rectangles tests eight corners and returns at the first hit, so
|
||||
* an overlap is cheap and a miss is the full eight. The third benchmark is the
|
||||
* broad phase the library does not provide: 64 actors is 2016 pairs, and a
|
||||
* caller doing collision at all does that every frame.
|
||||
*/
|
||||
/** @brief Counts nothing; the query cost is what is being measured. */
|
||||
static akerr_ErrorContext *bench_collision_visit(akgl_CollisionProxy *proxy, void *data)
|
||||
{
|
||||
@@ -895,11 +887,22 @@ static akerr_ErrorContext *bench_collision(void)
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Time akgl_collide_rectangles, and the all-pairs sweep it used to imply.
|
||||
*
|
||||
* The function is four span comparisons in `float32_t`, so an overlap and a miss
|
||||
* cost the same -- it has no early exit and does not want one.
|
||||
*
|
||||
* The last benchmark is the **control**, not a recommendation. It is what a game
|
||||
* had to write before 0.8.0: 64 actors is 2016 pairs, every frame. The
|
||||
* partitioner rows in bench_collision are what to compare it against, and the
|
||||
* comparison is generous to the control -- it tests pairs and does not sync,
|
||||
* sub-step or resolve anything.
|
||||
*/
|
||||
static akerr_ErrorContext *bench_geometry(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akerr_ErrorContext *inner = NULL;
|
||||
akgl_RectanglePoints points;
|
||||
SDL_FRect rects[BENCH_ACTOR_COUNT];
|
||||
SDL_FRect overlapping = { .x = 8.0, .y = 8.0, .w = 32.0, .h = 32.0 };
|
||||
SDL_FRect disjoint = { .x = 900.0, .y = 900.0, .w = 32.0, .h = 32.0 };
|
||||
@@ -922,11 +925,6 @@ static akerr_ErrorContext *bench_geometry(void)
|
||||
pairs = (BENCH_ACTOR_COUNT * (BENCH_ACTOR_COUNT - 1)) / 2;
|
||||
|
||||
for ( rep = 0; rep < AKGL_BENCH_REPETITIONS; rep++ ) {
|
||||
bench_start("rectangle_points", "call", 100.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_rectangle_points(&points, &subject));
|
||||
bench_stop(count);
|
||||
PASS(errctx, inner);
|
||||
|
||||
bench_start("collide_rectangles, overlapping", "call", 300.0);
|
||||
BENCH_LOOP(inner, i, count, akgl_collide_rectangles(&subject, &overlapping, &collide));
|
||||
bench_stop(count);
|
||||
|
||||
187
tests/util.c
187
tests/util.c
@@ -28,172 +28,6 @@ static int live_error_contexts(void)
|
||||
return live;
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
{
|
||||
akgl_RectanglePoints points;
|
||||
// Zeroed for the same reason as the fixtures in
|
||||
// test_akgl_collide_point_rectangle_nullpointers: the last case here is a
|
||||
// real call, and feeding it stack garbage is noise under `memcheck`.
|
||||
SDL_FRect testrect = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(NULL, NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with all NULL pointers");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(NULL, &testrect));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL SDL_FRect pointer");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&points, NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL akgl_RectanglePoints pointer");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&points, &testrect));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_rectangle_points_math(void)
|
||||
{
|
||||
akgl_RectanglePoints points;
|
||||
SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32};
|
||||
memset((void *)&points, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&points, &testrect));
|
||||
if ( points.topleft.x != 0 ||
|
||||
points.topleft.y != 0 ||
|
||||
points.topright.x != 32 ||
|
||||
points.topright.y != 0 ||
|
||||
points.bottomleft.x != 0 ||
|
||||
points.bottomleft.y != 32 ||
|
||||
points.bottomright.x != 32 ||
|
||||
points.bottomright.y != 32 ) {
|
||||
FAIL_BREAK(
|
||||
errctx,
|
||||
AKGL_ERR_BEHAVIOR,
|
||||
"akgl_rectangle_points incorrectly calculated points for {x=0, y=0, w=32, h=32} to {topleft={%d, %d}, topright={%d, %d}, bottomleft={%d, %d}, bottomright={%d, %d}}",
|
||||
points.topleft.x, points.topleft.y,
|
||||
points.topright.x, points.topright.y,
|
||||
points.bottomleft.x, points.bottomleft.y,
|
||||
points.bottomright.x, points.bottomright.y
|
||||
);
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
|
||||
{
|
||||
// Zeroed rather than left as whatever the stack held. The last case in this
|
||||
// function is a real call with real arguments, and reading uninitialised
|
||||
// floats out of it is sixteen findings under `memcheck` for a test that is
|
||||
// not about coordinates at all.
|
||||
akgl_Point testpoint = { .x = 0, .y = 0 };
|
||||
akgl_RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
memset(&testrectpoints, 0x00, sizeof(akgl_RectanglePoints));
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(*, *, NULL) failed");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, NULL, &testcollide));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(*, NULL, *) failed");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_collide_point_rectangle(NULL, &testrectpoints, &testcollide));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(NULL, *, *) failed");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_collide_point_rectangle(NULL, NULL, NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_collide_point_rectangle(NULL, NULL, NULL) failed");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_collide_point_rectangle_logic(void)
|
||||
{
|
||||
akgl_Point testpoint = {.x = 16, .y = 16};
|
||||
SDL_FRect testrect = { .x = 0, .y = 0, .w = 32, .h = 32};
|
||||
akgl_RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&testrectpoints, &testrect));
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
||||
if ( testcollide == false ) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Valid collision missed");
|
||||
}
|
||||
|
||||
testpoint.x = 48;
|
||||
testpoint.y = 48;
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
||||
if ( testcollide == true ) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Invalid collision reported");
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_collide_rectangles_nullpointers(void)
|
||||
{
|
||||
SDL_FRect testrect1 = {.x = 0, .y = 0, .w = 0, .h = 0};
|
||||
@@ -256,12 +90,12 @@ akerr_ErrorContext *test_akgl_collide_rectangles_nullpointers(void)
|
||||
* corner tests all report false. That was documented on akgl_collide_rectangles
|
||||
* as a known limitation rather than fixed, and there was no test for it.
|
||||
*
|
||||
* The three cases below the cross are the ones a rewrite can break while fixing
|
||||
* it: touching edges must keep counting as a collision, because
|
||||
* akgl_collide_point_rectangle is inclusive on all four edges and both headers
|
||||
* promise it; full containment must keep working; and a separation of less than
|
||||
* one pixel must be seen, which the old implementation could not do because it
|
||||
* routed through akgl_Point and truncated float to int.
|
||||
* The three cases below the cross are the ones the rewrite could have broken
|
||||
* while fixing it: touching edges must keep counting as a collision, because the
|
||||
* corner form was inclusive on all four edges and the header promised it; full
|
||||
* containment must keep working; and a separation of less than one pixel must be
|
||||
* seen, which the old implementation could not do because it routed through the
|
||||
* since-removed akgl_Point and truncated float to int.
|
||||
*/
|
||||
akerr_ErrorContext *test_akgl_collide_rectangles_arrangements(void)
|
||||
{
|
||||
@@ -295,8 +129,7 @@ akerr_ErrorContext *test_akgl_collide_rectangles_arrangements(void)
|
||||
CATCH(errctx, akgl_collide_rectangles(&inner, &outer, &collide));
|
||||
TEST_ASSERT(errctx, (collide == true), "a containing rectangle was missed");
|
||||
|
||||
// A shared edge and nothing more. akgl_collide_point_rectangle is
|
||||
// inclusive on all four edges, so touching counts; util.h and chapter 18
|
||||
// A shared edge and nothing more. Touching counts; util.h and chapter 19
|
||||
// both say so, and a span test written with < rather than <= silently
|
||||
// changes that.
|
||||
CATCH(errctx, akgl_collide_rectangles(&left, &right, &collide));
|
||||
@@ -540,12 +373,6 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, test_akgl_rectangle_points_nullpointers());
|
||||
CATCH(errctx, test_akgl_rectangle_points_math());
|
||||
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());
|
||||
// Defined since forever and never called until 0.5.0. TODO.md, "Known
|
||||
// and still open" item 9.
|
||||
CATCH(errctx, test_akgl_collide_point_rectangle_logic());
|
||||
CATCH(errctx, test_akgl_collide_rectangles_nullpointers());
|
||||
CATCH(errctx, test_akgl_collide_rectangles_logic());
|
||||
CATCH(errctx, test_akgl_collide_rectangles_arrangements());
|
||||
|
||||
Reference in New Issue
Block a user