Document what the functions actually do instead of that they can fail
The Doxygen comments were generated from the declarations, so 217 @throws lines across 21 headers read "When the corresponding validation or operation fails" and told a caller nothing beyond the status name. The @param lines were the same shape: every output was "Output destination populated by the function", every instance "Object to initialize, inspect, or modify". Rewritten against the implementations, following the pattern libakstdlib already uses: - @throws names the condition. akgl_sprite_load_json separated AKERR_KEY (absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS (filename too long, or array indexed past its end), and gained AKGL_ERR_SDL and AKGL_ERR_HEAP, which it raises and never declared. - Parameters say whether they are required, what a NULL means, and what is written on a failure path. Where an argument is not checked, the doc says so: akgl_heap_next_actor's dest is a crash on NULL, not an error, and akgl_render_2d_frame_start dereferences self before testing it. - The conventions move up to the file blocks so the per-function docs stay short. json_helpers.h states once that absence is an error here and that json_t * results are borrowed; heap.h explains the pool model and the acquire asymmetry; physics.h carries the thrust/environmental/velocity table. - Struct fields, enum values, macros and exported globals are documented, including the dead ones - sprite_w/sprite_h, movetimer, p_scale and timer_gravity are read by nothing, and say so. Also fixes ten comments in error.h and audio.h that opened with /** rather than /**<, so Doxygen attached them to the following entity and rendered the text as part of the macro's value. Verified against the generated HTML. Comments only - no declaration changed. Doxygen builds clean under WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,28 @@
|
||||
/**
|
||||
* @file physics.h
|
||||
* @brief Declares the public physics API.
|
||||
* @brief The pluggable physics backend, and the two implementations that ship.
|
||||
*
|
||||
* Same shape as the renderer: a record of function pointers plus an initializer
|
||||
* that fills it in. `null` accepts every call and changes nothing, which is what
|
||||
* a menu screen or a test harness wants; `arcade` applies gravity, drag, thrust
|
||||
* and a speed cap. Selecting between them is akgl_physics_factory, driven by the
|
||||
* `physics.engine` configuration property -- not a branch in the simulation.
|
||||
*
|
||||
* The model an actor is simulated under, and the fields on akgl_Actor that carry
|
||||
* it:
|
||||
*
|
||||
* | Term | Fields | Comes from |
|
||||
* |-----------------------|--------------|-----------------------------------------------|
|
||||
* | thrust | `tx, ty, tz` | the actor's own acceleration while it is moving |
|
||||
* | environmental | `ex, ey, ez` | gravity, less drag |
|
||||
* | velocity | `vx, vy, vz` | thrust + environmental |
|
||||
* | max speed | `sx, sy, sz` | the character; caps thrust, not velocity |
|
||||
* | acceleration | `ax, ay, az` | the character |
|
||||
*
|
||||
* Each step: movement logic, then gravity, then drag, then velocity, then move.
|
||||
* Because the cap is applied to thrust rather than to velocity, gravity can
|
||||
* carry an actor faster than its stated top speed -- which is the point, for a
|
||||
* falling one.
|
||||
*/
|
||||
|
||||
#ifndef _PHYSICS_H_
|
||||
@@ -14,110 +36,202 @@
|
||||
|
||||
/** @brief Defines a pluggable physics backend and its environmental parameters. */
|
||||
typedef struct akgl_PhysicsBackend {
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*simulate)(struct akgl_PhysicsBackend *self, akgl_Iterator *opflags);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*gravity)(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*collide)(struct akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*move)(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt);
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*simulate)(struct akgl_PhysicsBackend *self, akgl_Iterator *opflags); /**< Step the whole world. Both backends point this at akgl_physics_simulate. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*gravity)(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt); /**< Apply environmental acceleration to one actor. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*collide)(struct akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2); /**< Resolve a collision between two actors. Not called by the simulation loop yet. */
|
||||
akerr_ErrorContext AKERR_NOIGNORE *(*move)(struct akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt); /**< Commit one actor's velocity to its position. */
|
||||
|
||||
double drag_x;
|
||||
double drag_y;
|
||||
double drag_z;
|
||||
double gravity_x;
|
||||
double gravity_y;
|
||||
double gravity_z;
|
||||
SDL_Time gravity_time;
|
||||
SDL_Time timer_gravity;
|
||||
double drag_x; /**< Fraction of environmental velocity shed per second along x. 0 disables. From `physics.drag.x`. */
|
||||
double drag_y; /**< Drag along y. From `physics.drag.y`. */
|
||||
double drag_z; /**< Drag along z. From `physics.drag.z`. */
|
||||
double gravity_x; /**< Acceleration along x, world units per second squared. Applied toward screen left. From `physics.gravity.x`. */
|
||||
double gravity_y; /**< Acceleration along y. Applied down the screen, since y grows downward. From `physics.gravity.y`. */
|
||||
double gravity_z; /**< Acceleration along z. Applied away from the camera. From `physics.gravity.z`. */
|
||||
SDL_Time gravity_time; /**< Timestamp of the previous step, in nanoseconds. The simulation's `dt` is measured from this. */
|
||||
SDL_Time timer_gravity; /**< Unused. Nothing in the library reads or writes it. */
|
||||
} akgl_PhysicsBackend;
|
||||
|
||||
/**
|
||||
* @brief Physics null gravity.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param actor Actor to inspect or modify.
|
||||
* @param dt Elapsed simulation time in seconds.
|
||||
* @brief Gravity for the null backend: accept the call and change nothing.
|
||||
* @param self The backend. Required -- the one thing this does check.
|
||||
* @param actor The actor that would have been accelerated. Ignored; `NULL` is
|
||||
* accepted.
|
||||
* @param dt Seconds since the previous step. Ignored.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt);
|
||||
/**
|
||||
* @brief Physics null collide.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param a1 First actor supplied to collision handling.
|
||||
* @param a2 Second actor supplied to collision handling.
|
||||
* @brief Collision for the null backend: accept the call and change nothing.
|
||||
*
|
||||
* Note that this *succeeds* where the arcade backend's collide refuses -- the
|
||||
* null backend's contract is "nothing collides", which is an answer, not a gap.
|
||||
*
|
||||
* @param self The backend. Required.
|
||||
* @param a1 First actor. Ignored; `NULL` is accepted.
|
||||
* @param a2 Second actor. Ignored; `NULL` is accepted.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2);
|
||||
/**
|
||||
* @brief Physics null move.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param actor Actor to inspect or modify.
|
||||
* @param dt Elapsed simulation time in seconds.
|
||||
* @brief Movement for the null backend: accept the call and change nothing.
|
||||
*
|
||||
* Actors under the null backend do not move on their own. Anything that sets an
|
||||
* actor's `x`/`y` directly still works -- this only declines to integrate
|
||||
* velocity.
|
||||
*
|
||||
* @param self The backend. Required.
|
||||
* @param actor The actor that would have moved. Ignored; `NULL` is accepted.
|
||||
* @param dt Seconds since the previous step. Ignored.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_null_move(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt);
|
||||
/**
|
||||
* @brief Physics init null.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @brief Install the null backend's methods.
|
||||
*
|
||||
* Reads no configuration and touches no gravity or drag fields, so whatever was
|
||||
* in them stays -- harmlessly, since none of the null methods look.
|
||||
*
|
||||
* @param self The backend to initialize. Required.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_null(akgl_PhysicsBackend *self);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Physics arcade gravity.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param actor Actor to inspect or modify.
|
||||
* @param dt Elapsed simulation time in seconds.
|
||||
* @brief Accelerate one actor's environmental velocity by gravity.
|
||||
*
|
||||
* Adds `gravity * dt` to the actor's `ex`/`ey`/`ez` on each axis whose gravity
|
||||
* is non-zero, with the sign chosen for screen space: x pulls left, y pulls
|
||||
* down, z pulls away from the camera. It touches only the environmental term, so
|
||||
* an actor's own thrust is unaffected and the speed cap does not apply -- which
|
||||
* is why a falling actor keeps accelerating past its character's top speed.
|
||||
*
|
||||
* @param self The backend supplying the gravity constants. Required.
|
||||
* @param actor The actor to accelerate. Required.
|
||||
* @param dt Seconds since the previous step. A `dt` of 0 is a no-op; a
|
||||
* negative one accelerates backwards.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self or @p actor is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_gravity(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt);
|
||||
/**
|
||||
* @brief Physics arcade collide.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param a1 First actor supplied to collision handling.
|
||||
* @param a2 Second actor supplied to collision handling.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_API When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @brief Collision for the arcade backend. Not implemented.
|
||||
*
|
||||
* Deliberately loud rather than silently permissive: unlike
|
||||
* akgl_physics_null_collide, which means "nothing collides", this means "nobody
|
||||
* has written this yet", and a caller that reaches it should find out.
|
||||
*
|
||||
* @param self The backend. Required, and checked before the refusal.
|
||||
* @param a1 First actor. Never examined.
|
||||
* @param a2 Second actor. Never examined.
|
||||
* @return Never `NULL`.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
* @throws AKERR_API Otherwise, always, with the message "Not implemented".
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_collide(akgl_PhysicsBackend *self, akgl_Actor *a1, akgl_Actor *a2);
|
||||
/**
|
||||
* @brief Physics arcade move.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param actor Actor to inspect or modify.
|
||||
* @param dt Elapsed simulation time in seconds.
|
||||
* @brief Commit an actor's velocity to its position.
|
||||
*
|
||||
* The last step of a simulation tick: `position += velocity * dt` on all three
|
||||
* axes. It does not clamp to the map, consult the tilemap, or test for
|
||||
* collisions -- an actor will walk straight through a wall and off the edge of
|
||||
* the world.
|
||||
*
|
||||
* @param self The backend. Required, though nothing on it is read.
|
||||
* @param actor The actor to move. Required. Its `vx`/`vy`/`vz` must already have
|
||||
* been computed; akgl_physics_simulate does that immediately
|
||||
* before calling this.
|
||||
* @param dt Seconds since the previous step.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self or @p actor is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_move(akgl_PhysicsBackend *self, akgl_Actor *actor, float32_t dt);
|
||||
/**
|
||||
* @brief Physics init arcade.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @brief Install the arcade backend's methods and read its constants from configuration.
|
||||
*
|
||||
* Reads `physics.gravity.x`, `.y`, `.z` and `physics.drag.x`, `.y`, `.z` from
|
||||
* the property registry, all defaulting to `"0.0"` -- so an arcade backend with
|
||||
* no configuration behaves like the null one until something is set.
|
||||
*
|
||||
* @param self The backend to initialize. Required. Its method pointers are
|
||||
* installed before the properties are read, so a failure part-way
|
||||
* leaves a usable backend with some constants still at their
|
||||
* previous values.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self is `NULL`.
|
||||
* @throws AKERR_VALUE If one of the six properties is set to something that is
|
||||
* not a number.
|
||||
* @throws ERANGE If one of them does not fit a `double`.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*
|
||||
* @note With #AKGL_REGISTRY_PROPERTIES uninitialized every property reads back
|
||||
* as its default, so this silently configures zero gravity and zero drag
|
||||
* rather than reporting anything. See the warning in registry.h.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend *self);
|
||||
|
||||
/**
|
||||
* @brief Physics factory.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param type Expected JSON property type or backend type name.
|
||||
* @brief Select and initialize a physics backend by name.
|
||||
*
|
||||
* The dispatch point for the whole subsystem: `"null"` and `"arcade"` are the
|
||||
* two names, and akgl_game_init passes whatever the `physics.engine` property
|
||||
* holds. Adding a backend means adding a name here, not a branch in the
|
||||
* simulation.
|
||||
*
|
||||
* @param self The backend to initialize. Required.
|
||||
* @param type The backend name. Required. Matched on its leading characters, so
|
||||
* `"nullify"` selects `null` and `"arcadia"` selects `arcade`.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_KEY When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self or @p type is `NULL`.
|
||||
* @throws AKERR_KEY If @p type matches neither name. The message quotes what was
|
||||
* asked for.
|
||||
* @throws AKERR_* Whatever the selected initializer raises.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *self, akgl_String *type);
|
||||
|
||||
/**
|
||||
* @brief Physics simulate.
|
||||
* @param self Backend or object instance to operate on.
|
||||
* @param opflags Optional iterator operation flags; `NULL` selects defaults.
|
||||
* @brief Step every live actor forward by the time elapsed since the previous call.
|
||||
*
|
||||
* Shared by both backends -- what differs is the `gravity` and `move` they point
|
||||
* at. For each live actor, in pool order:
|
||||
*
|
||||
* - a child actor is snapped to its parent's position plus its own `v` as an
|
||||
* offset, and skipped entirely -- children do not simulate;
|
||||
* - an actor with no character is skipped, since the speed and acceleration
|
||||
* constants live there;
|
||||
* - thrust accumulates along an axis the actor is moving on, then is clamped to
|
||||
* the character's maximum speed;
|
||||
* - the backend's `gravity` runs, drag is applied to the environmental term,
|
||||
* velocity becomes environmental plus thrust, and the backend's `move`
|
||||
* commits it.
|
||||
*
|
||||
* `dt` is measured from `self->gravity_time`, which is stamped at the end. The
|
||||
* first call after initialization therefore measures from 0 and produces an
|
||||
* enormous `dt` -- initialize `gravity_time` from `SDL_GetTicksNS()` before the
|
||||
* first step if that matters.
|
||||
*
|
||||
* @param self The backend. Required, along with its `move` pointer.
|
||||
* @param opflags Iterator flags. Optional -- `NULL` means "simulate everything".
|
||||
* Only #AKGL_ITERATOR_OP_LAYERMASK is honoured, restricting the
|
||||
* step to actors on `layerid`; the update, render, and release
|
||||
* bits are ignored here.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKGL_ERR_LOGICINTERRUPT When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p self or `self->move` is `NULL`.
|
||||
* @throws AKERR_* Whatever an actor's `movementlogicfunc`, or the backend's
|
||||
* `gravity` or `move`, raises. The first failure aborts the whole step,
|
||||
* leaving the actors already processed advanced and the rest not.
|
||||
*
|
||||
* @note An actor's `movementlogicfunc` can raise AKGL_ERR_LOGICINTERRUPT to say
|
||||
* "skip the rest of the simulation for me this tick". It is handled here
|
||||
* and does not propagate -- it is a control signal wearing an error's
|
||||
* clothes, not a failure. Only `movementlogicfunc` gets that treatment:
|
||||
* the `gravity` and `move` calls return straight out of the loop on any
|
||||
* status, LOGICINTERRUPT included, so a backend must not use it to opt an
|
||||
* actor out from there.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *self, akgl_Iterator *opflags);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user