Files
libakgl/include/akgl/actor.h
Andrew Kesterson d0b057f47a Pool collision proxies, and tie one to the life of its actor
A proxy is what the broad phase will index: an actor's shape, where it is, and
the bounds that follow from those. It is a sixth heap layer, two per actor --
one for the actor itself and headroom for static geometry a game registers that
is not tile-aligned. Solid *tiles* deliberately get none: the broad phase will
read those out of the tilemap's own array, because one proxy per tile is tens of
megabytes to index data that is already a grid.

The pool follows the existing convention rather than improving on it.
akgl_heap_next_collision_proxy finds a free slot and does not claim it;
akgl_collision_proxy_initialize claims it. That is TODO.md item 8's asymmetry,
and it is kept on purpose: an acquire abandoned before initialization leaks
nothing, because the slot still reads as free, and a sixth pool with its own
rule would be worse than one defect with five instances. Fixing item 8 means
fixing all five together with every call site audited, and that is its own
change.

The proxy holds a *copy* of the shape rather than a pointer. An actor's own
logic may rewrite its shape mid-step -- a character crouching, a projectile
arming -- and a broad phase whose bounds came from a shape that has since
changed is a bug that only appears when two things happen in the same frame.

akgl_heap_release_actor now releases the actor's proxy. Without it the proxy
holds a borrowed `owner` into a slot that has just been zeroed, so the broad
phase keeps a registration whose owner reads as a free actor, and the next
contact against it is a collision with nothing. Verified by removing the release
and watching test_proxy_dies_with_its_actor go red.

The tests pin the convention as well as the behaviour: that two acquires with no
initialize between them return the same slot is asserted, not merely tolerated,
so that changing the convention has to change a test that says why it exists.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 23:56:21 -04:00

459 lines
29 KiB
C

/**
* @file actor.h
* @brief A live thing in the world: state bits, motion, hierarchy, and behaviour hooks.
*
* An actor is an instance; the akgl_Character it points at is its template. The
* actor carries only what is unique to it -- where it is, which way it is
* moving, which animation frame it is on -- and borrows speed, acceleration and
* the state-to-sprite map from the character. That is what makes a hundred of
* one kind of thing cheap.
*
* **State is a bitmask**, not an enum. An actor is facing left *and* moving left
* *and* alive at the same time, and the whole combination is the key that
* selects a sprite (see akgl_character_sprite_get). Adding a state means adding
* a bit here *and* a name in `src/actor_state_string_names.c`, or character JSON
* cannot refer to it.
*
* **Behaviour attaches as function pointers**, not by inheritance. Every actor
* gets the library's default `updatefunc`, `renderfunc`, `facefunc`,
* `movementlogicfunc`, `changeframefunc` and `addchild` from
* akgl_actor_initialize; a game that wants a different flavour of anything
* replaces the pointer on that one actor.
*
* **Motion is split four ways** -- thrust, environmental, acceleration, and the
* velocity that is their sum -- so that a character's own walking and the
* world's gravity and drag can be reasoned about separately. See physics.h for
* the model and which fields mean what.
*
* Actors are pool objects (akgl_heap_next_actor) published in
* #AKGL_REGISTRY_ACTOR under their name.
*/
#ifndef _AKGL_ACTOR_H_
#define _AKGL_ACTOR_H_
#include <stdint.h>
#include <akgl/types.h>
#include <akgl/character.h>
// ---- LOW WORD STATUSES ----
//
// The trailing column is the bit pattern within its own 16-bit half -- the
// low half here, the high half below -- not the full 32-bit value. Read as a
// whole word it looks like bit 16 restarts at bit 0, which it does not.
#define AKGL_ACTOR_STATE_FACE_DOWN (1 << 0) // 1 0000 0000 0000 0001
#define AKGL_ACTOR_STATE_FACE_LEFT (1 << 1) // 2 0000 0000 0000 0010
#define AKGL_ACTOR_STATE_FACE_RIGHT (1 << 2) // 4 0000 0000 0000 0100
#define AKGL_ACTOR_STATE_FACE_UP (1 << 3) // 8 0000 0000 0000 1000
#define AKGL_ACTOR_STATE_ALIVE (1 << 4) // 16 0000 0000 0001 0000
#define AKGL_ACTOR_STATE_DYING (1 << 5) // 32 0000 0000 0010 0000
#define AKGL_ACTOR_STATE_DEAD (1 << 6) // 64 0000 0000 0100 0000
#define AKGL_ACTOR_STATE_MOVING_LEFT (1 << 7) // 128 0000 0000 1000 0000
#define AKGL_ACTOR_STATE_MOVING_RIGHT (1 << 8) // 256 0000 0001 0000 0000
#define AKGL_ACTOR_STATE_MOVING_UP (1 << 9) // 512 0000 0010 0000 0000
#define AKGL_ACTOR_STATE_MOVING_DOWN (1 << 10) // 1024 0000 0100 0000 0000
#define AKGL_ACTOR_STATE_MOVING_IN (1 << 11) // 2048 0000 1000 0000 0000
#define AKGL_ACTOR_STATE_MOVING_OUT (1 << 12) // 4096 0001 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_13 (1 << 13) // 8192 0010 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_14 (1 << 14) // 16384 0100 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_15 (1 << 15) // 32768 1000 0000 0000 0000
// ----- HIGH WORD STATUSES -----
#define AKGL_ACTOR_STATE_UNDEFINED_16 (1 << 16) // 65536 0000 0000 0000 0001
#define AKGL_ACTOR_STATE_UNDEFINED_17 (1 << 17) // 131072 0000 0000 0000 0010
#define AKGL_ACTOR_STATE_UNDEFINED_18 (1 << 18) // 262144 0000 0000 0000 0100
#define AKGL_ACTOR_STATE_UNDEFINED_19 (1 << 19) // 524288 0000 0000 0000 1000
#define AKGL_ACTOR_STATE_UNDEFINED_20 (1 << 20) // 1048576 0000 0000 0001 0000
#define AKGL_ACTOR_STATE_UNDEFINED_21 (1 << 21) // 2097152 0000 0000 0010 0000
#define AKGL_ACTOR_STATE_UNDEFINED_22 (1 << 22) // 4194304 0000 0000 0100 0000
#define AKGL_ACTOR_STATE_UNDEFINED_23 (1 << 23) // 8388608 0000 0000 1000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_24 (1 << 24) // 16777216 0000 0001 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_25 (1 << 25) // 33554432 0000 0010 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_26 (1 << 26) // 67108864 0000 0100 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_27 (1 << 27) // 134217728 0000 1000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_28 (1 << 28) // 268435456 0001 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_29 (1 << 29) // 536870912 0010 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_30 (1 << 30) // 1073741824 0100 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_31 (1 << 31) // 2147483648 1000 0000 0000 0000
/** @brief Bits in an actor's state word. Fixed by the width of `int32_t state`. */
#define AKGL_ACTOR_MAX_STATES 32
/**
* @brief Bit position -> state name, as text. Index `i` names the bit `1 << i`.
*
* akgl_registry_init_actor_state_strings builds
* #AKGL_REGISTRY_ACTOR_STATE_STRINGS out of this, which is what lets character
* JSON write `"AKGL_ACTOR_STATE_FACE_LEFT"` instead of `2`. A bit whose name
* here does not match its `#define` above cannot be referred to from JSON at
* all -- which was the case for `MOVING_IN` and `MOVING_OUT` until 0.5.0.
*
* Maintained by hand in `src/actor_state_string_names.c`, and exactly
* #AKGL_ACTOR_MAX_STATES entries long: it was declared one longer than it was
* defined until 0.5.0, so a consumer trusting the declared bound read past the
* object. `tests/registry.c` checks every entry against its bit.
*/
extern char *AKGL_ACTOR_STATE_STRING_NAMES[AKGL_ACTOR_MAX_STATES];
/** @brief Every facing bit. Clear this before setting one, so an actor faces exactly one way. */
#define AKGL_ACTOR_STATE_FACE_ALL (AKGL_ACTOR_STATE_FACE_DOWN | AKGL_ACTOR_STATE_FACE_LEFT | AKGL_ACTOR_STATE_FACE_RIGHT | AKGL_ACTOR_STATE_FACE_UP)
/** @brief Every planar movement bit. The two depth bits, MOVING_IN and MOVING_OUT, are not in it. */
#define AKGL_ACTOR_STATE_MOVING_ALL (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_MOVING_UP | AKGL_ACTOR_STATE_MOVING_DOWN)
/** @brief Longest actor name, including the terminator. Names are truncated, not rejected. */
#define AKGL_ACTOR_MAX_NAME_LENGTH 128
/** @brief Children one actor can carry. A child moves with its parent rather than simulating. */
#define AKGL_ACTOR_MAX_CHILDREN 8
/** @brief Represents a live actor, including state, motion, hierarchy, and behavior callbacks. */
typedef struct akgl_Actor {
uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. One reference per parent, plus one for the registry. */
char name[AKGL_ACTOR_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
akgl_Character *basechar; /**< The template this actor instantiates. Borrowed; no reference is taken. Required before the actor can update, render, or simulate. */
uint8_t curSpriteFrameId; /**< Index into the current sprite's `frameids`, not a frame number on the sheet. */
SDL_Time curSpriteFrameTimer; /**< When the current frame was shown. The next frame is due once the sprite's `speed` has elapsed. */
bool curSpriteReversing; /**< Walking the animation backwards, for a sprite with `loopReverse` set. */
uint32_t layer; /**< Which tilemap layer the actor is drawn and simulated on. Set from the object layer it was placed in. */
int32_t state; /**< The `AKGL_ACTOR_STATE_*` bitmask. The whole value is the key that selects a sprite. */
bool movement_controls_face; /**< When set, the default `facefunc` turns the actor to face whichever way it is moving. Clear it for an actor that aims independently. */
void *actorData; /**< The game's own per-actor data. Never read or freed by the library. */
bool visible; /**< Whether to draw at all. An actor off-camera is skipped separately, so this is for deliberate hiding. */
SDL_Time movetimer; /**< Unused. Nothing in the library reads or writes it. */
akgl_CollisionShape shape; /**< The collision volume this actor occupies. Copied from the character by akgl_actor_set_character unless #shape_override is set. Zeroed by akgl_actor_initialize, so an actor nobody configured does not collide. */
bool shape_override; /**< The game set #shape itself and akgl_actor_set_character must leave it alone. Distinct from "the shape is empty": an actor deliberately given no collider and an actor not yet given one are different states, and only this flag tells them apart. */
struct akgl_CollisionProxy *proxy; /**< This actor's registration in the broad phase, or `NULL` when it has none. Owned by the library and released with the actor; a game neither sets nor frees it. */
// Velocity. Combined effect of all forces acting on the actor resulting
// in energy along an axis.
float32_t vx; /**< Velocity along x, world units per second. Recomputed each step as `ex + tx`; writing it directly is overwritten. */
float32_t vy; /**< Velocity along y. On a child actor this is read as an offset from the parent instead. */
float32_t vz; /**< Velocity along z. */
// Environmental velocity. These are the forces acting on the actor by the
// environment (such as gravity and atmospheric drag)
float32_t ex; /**< Environmental velocity along x: what gravity and drag have done. Accumulates across steps. */
float32_t ey; /**< Environmental velocity along y. This is what carries a falling actor. */
float32_t ez; /**< Environmental velocity along z. */
// Thrust. Energy originating only from the actor's own acceleration on a
// given axis, before the effects of gravity and drag.
float32_t tx; /**< Thrust along x: the actor's own effort. Capped at `sx`, which is why gravity can outrun top speed and walking cannot. */
float32_t ty; /**< Thrust along y, capped at `sy`. */
float32_t tz; /**< Thrust along z, capped at `sz`. */
// Acceleration. These are borrowed from the base character object.
// A given axis resets to 0 when the actor stops moving in a given axis.
float32_t ax; /**< Acceleration along x, signed by the direction of travel. Copied from the character each step by the default movement logic. */
float32_t ay; /**< Acceleration along y, signed the same way. */
float32_t az; /**< Acceleration along z. Not set by the default movement logic. */
// Max speed. These are borrowed from the base character object.
float32_t sx; /**< Maximum thrust along x, copied from the character. */
float32_t sy; /**< Maximum thrust along y. */
float32_t sz; /**< Maximum thrust along z. */
// Position.
float32_t x; /**< Position in map pixels. For a child, an offset from the parent. */
float32_t y; /**< Position in map pixels. Also what the tilemap's perspective band scales against. */
float32_t z; /**< Depth. Carried through the simulation but not used at draw time. */
float32_t scale; /**< Draw scale, 1.0 for natural size. Written by akgl_tilemap_scale_actor, or forced to 1.0 when tilemap scaling is off. */
struct akgl_Actor *children[AKGL_ACTOR_MAX_CHILDREN]; /**< Attached actors, `NULL` in unused slots. Each holds a reference and is released with the parent. */
struct akgl_Actor *parent; /**< The actor this one is attached to, or `NULL`. A child is positioned relative to it and does not simulate on its own. */
akerr_ErrorContext AKERR_NOIGNORE *(*updatefunc)(struct akgl_Actor *obj); /**< Per-frame logic: facing, then animation frame. Defaults to akgl_actor_update. */
akerr_ErrorContext AKERR_NOIGNORE *(*renderfunc)(struct akgl_Actor *obj); /**< Per-frame draw. Defaults to akgl_actor_render. */
akerr_ErrorContext AKERR_NOIGNORE *(*facefunc)(struct akgl_Actor *obj); /**< Chooses the facing bits. Defaults to akgl_actor_automatic_face. */
akerr_ErrorContext AKERR_NOIGNORE *(*movementlogicfunc)(struct akgl_Actor *obj, float32_t dt); /**< Called by the physics step before gravity. Defaults to akgl_actor_logic_movement. May raise AKGL_ERR_LOGICINTERRUPT to skip the rest of the step. */
akerr_ErrorContext AKERR_NOIGNORE *(*changeframefunc)(struct akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems); /**< Advances the animation. Defaults to akgl_actor_logic_changeframe. */
akerr_ErrorContext AKERR_NOIGNORE *(*addchild)(struct akgl_Actor *obj, struct akgl_Actor *child); /**< Attaches a child. Defaults to akgl_actor_add_child. */
} akgl_Actor;
/**
* @brief Zero a pooled actor, name it, wire up its default behaviour, and register it.
*
* Sets `scale` to 1.0 and `movement_controls_face` to true, installs all six
* default function pointers, publishes the actor in #AKGL_REGISTRY_ACTOR, and
* takes the first reference. It does *not* set a character: the actor cannot
* update, render, or simulate until akgl_actor_set_character has run.
*
* @param obj Pooled actor to initialize, normally from akgl_heap_next_actor.
* Required. Any previous contents are discarded -- including its
* child list, so release an actor rather than reinitializing it.
* @param name Registry key, NUL-terminated. Required. Truncated at
* #AKGL_ACTOR_MAX_NAME_LENGTH. An existing entry with the same name
* is silently replaced, and the actor it displaced becomes
* unreachable rather than being released.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p name is `NULL`.
* @throws AKERR_KEY If the actor cannot be written into #AKGL_REGISTRY_ACTOR --
* in practice, because akgl_registry_init has not run.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_initialize(akgl_Actor *obj, char *name);
/**
* @brief Bind an actor to a registered character, and copy its top speeds.
*
* Looks the character up by name and takes its `sx`/`sy` as this actor's speed
* limits, zeroing `ax` and `ay` so the actor starts from rest. The character is
* borrowed, not referenced: releasing it out from under a live actor leaves a
* dangling pointer.
*
* @param obj The actor to bind. Required.
* @param basecharname Registry name of the character. Required. Rebinding an
* actor mid-life is allowed, and is how a character swaps
* its whole sprite set at once.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p basecharname is `NULL`, or if no
* character is registered under that name. The last case is a lookup
* miss reported with a pointer status rather than AKERR_KEY.
*
* @note `sz` is not copied and `az` is not zeroed, so an actor's depth speed
* keeps whatever it had. The default movement logic re-copies all three
* each step, which papers over it for anything that simulates.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_set_character(akgl_Actor *obj, char *basecharname);
/**
* @brief Draw an actor's current animation frame, if it is on camera.
*
* Selects the sprite for the actor's current state, checks it against the
* camera, and blits the frame at the actor's position translated into screen
* space. A child actor is drawn at its parent's position plus its own, which is
* what makes an offset mean an offset.
*
* Several things are skipped rather than reported, because a frame is not the
* place to fail: an actor with no sprite for its state, one off camera, one with
* `visible` clear, and one whose frame index has run past its current sprite
* are all simply not drawn.
*
* @param obj The actor to draw. Required, along with its `basechar`.
* @return `NULL` on success -- including every skipped case above -- otherwise
* an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
* @throws AKERR_* Whatever akgl_spritesheet_coords_for_frame or the renderer's
* `draw_texture` raises.
*
* @note The destination height is computed from the sprite's *width*, so a
* non-square sprite is drawn square.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_render(akgl_Actor *obj);
/**
* @brief Per-frame logic: turn the actor to face its direction, then advance its animation.
*
* Calls the actor's `facefunc`, then selects the sprite for the resulting state
* and calls `changeframefunc` if that sprite's frame time has elapsed. An actor
* with no sprite registered for its current state is left alone and reported as
* success -- states change faster than art gets drawn, and a missing sprite
* should not stop the frame.
*
* @param obj The actor to update. Required, along with its `basechar`.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
* @throws AKERR_* Whatever `facefunc` or `changeframefunc` raises. Note that an
* AKERR_KEY from either is swallowed along with the missing-sprite case.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_update(akgl_Actor *obj);
/**
* @brief The default `movementlogicfunc`: turn movement bits into signed acceleration.
*
* Re-copies the character's speed limits onto the actor -- so a change to the
* character takes effect next step -- and sets `ax`/`ay` to plus or minus the
* character's acceleration according to which movement bits are set. It does not
* integrate anything; the physics backend does that.
*
* Opposing bits do not cancel: `MOVING_LEFT` wins over `MOVING_RIGHT` because it
* is tested first. An actor with no movement bits keeps its previous
* acceleration rather than being zeroed, which is why the input handlers clear
* it themselves on key release.
*
* @param obj The actor to compute acceleration for. Required, along with its
* `basechar`.
* @param dt Seconds since the previous step. Accepted for the hook's signature;
* this implementation does not use it, since it sets an acceleration
* rather than integrating one.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
*
* @note A replacement for this hook may raise AKGL_ERR_LOGICINTERRUPT to tell
* the physics step to skip the rest of this actor's tick. This
* implementation never does.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_logic_movement(akgl_Actor *obj, float32_t dt);
/**
* @brief The default `changeframefunc`: step to the next animation frame.
*
* Walks forward through the sprite's frames; at the last one it either wraps to
* 0, or -- for a sprite with both `loop` and `loopReverse` -- turns round and
* walks back down, turning again at frame 0. A sprite with `loop` clear also
* wraps to 0 rather than holding the final frame.
*
* @param obj The actor whose frame index to advance. Required.
* @param curSprite The sprite currently selected for the actor, whose frame
* count and loop flags decide what happens at the end.
* Required in practice, and **not** checked -- a `NULL` here is
* a crash, not an error.
* @param curtimems The current clock reading. Accepted for the hook's signature;
* this implementation does not use it, because the caller has
* already decided the frame is due.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_logic_changeframe(akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems);
/**
* @brief The default `facefunc`: turn the actor to face whichever way it is moving.
*
* Clears every facing bit and sets the one matching the first movement bit it
* finds, in the order left, right, up, down -- so an actor moving diagonally
* faces the horizontal component. An actor with `movement_controls_face` clear
* is left alone entirely, which is the hook for something that aims
* independently of the way it walks.
*
* @param obj The actor to turn. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
*
* @note An actor that has stopped moving is left with no facing bit at all,
* rather than keeping the way it was last facing -- so its state no longer
* matches any "standing still facing left" sprite. The implementation
* carries a TODO saying as much.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_automatic_face(akgl_Actor *obj);
/**
* @brief Attach one actor to another so it moves with it.
*
* A child is positioned relative to its parent rather than simulated: the
* physics step snaps it to the parent's position plus its own velocity fields,
* used here as a fixed offset. That is what a carried lantern or a turret on a
* tank wants.
*
* The parent takes a reference on the child, and releasing the parent releases
* every child with it.
*
* @param obj The parent. Required.
* @param child The actor to attach. Required. Must not already have a parent --
* this builds a tree, not a graph. Its `x`/`y` become an offset
* from the parent's position rather than a world position.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p child is `NULL`.
* @throws AKERR_RELATIONSHIP If @p child already has a parent. Detach it first --
* though note there is no detach function, so in practice this means
* releasing and rebuilding it.
* @throws AKERR_OUTOFBOUNDS If @p obj already has #AKGL_ACTOR_MAX_CHILDREN
* children.
*
* @warning Nothing checks for a cycle. Making an actor its own ancestor makes
* akgl_heap_release_actor recurse until the stack runs out.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_add_child(akgl_Actor *obj, akgl_Actor *child);
/*
* The control-map handler functions ("cmhf"). These are what
* akgl_controller_default binds the arrow keys and the D-pad to, and what a
* game's own akgl_Control bindings can point at. They are called with the
* control map's target actor, not with a global "player", so they work for any
* number of locally controlled actors.
*
* Each pair is symmetric: the `_on` handler starts movement in a direction and
* turns the actor to face it, and the `_off` handler stops it dead -- zeroing
* acceleration, thrust, environmental velocity and velocity on that axis, so
* there is no coasting. That is an arcade feel rather than a physical one, and
* it is deliberate; a game wanting momentum binds its own handlers instead.
*
* An `_on` handler clears *every* facing and movement bit before setting its
* own, so holding two directions at once moves in whichever was pressed last
* rather than diagonally.
*/
/**
* @brief Start the actor moving left, and turn it to face left.
* @param obj The actor to move, supplied by the control map. Required, along
* with its `basechar`, whose acceleration is negated onto `ax`.
* @param event The event that triggered this. Required, but not read -- the
* binding has already decided what the event means.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving left, zeroing everything on the x axis.
* @param obj The actor to stop. Required. `basechar` is not needed here.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note It clears the x axis whichever way the actor was going, so releasing
* left while holding right also stops the rightward movement.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving right, and turn it to face right.
* @param obj The actor to move. Required, along with its `basechar`.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving right, zeroing everything on the x axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving up the screen, and turn it to face up.
* @param obj The actor to move. Required, along with its `basechar`, whose y
* acceleration is negated -- y grows downward.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving up, zeroing everything on the y axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note This also zeroes `ey`, so an actor under gravity has its accumulated
* fall cancelled by releasing a movement key.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving down the screen, and turn it to face down.
* @param obj The actor to move. Required, along with its `basechar`.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving down, zeroing everything on the y axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note Zeroes `ey` as well; see akgl_actor_cmhf_up_off.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one registered actor.
*
* Runs the operations the iterator asks for, in a fixed order regardless of the
* order the bits were set: layer filter, update, tilemap scaling, render. With
* #AKGL_ITERATOR_OP_LAYERMASK set, an actor on any other layer is skipped
* entirely; with #AKGL_ITERATOR_OP_TILEMAPSCALE clear, `scale` is forced to 1.0
* rather than left alone.
*
* @param userdata The akgl_Iterator carrying the operation flags, passed through
* by `SDL_EnumerateProperties`. Required despite the `void *`.
* @param registry #AKGL_REGISTRY_ACTOR, supplied by SDL.
* @param name The actor's registry key. Required.
*
* @warning This is an SDL callback, so it returns `void` and has nowhere to
* propagate an error to. It ends in `FINISH_NORETURN`, which logs the
* stack trace and then calls libakerror's unhandled-error handler --
* whose default implementation **exits the process** with the error
* status. An actor whose `updatefunc` fails therefore terminates the
* game rather than skipping a frame. Install your own
* `akerr_handler_unhandled_error` if that is not what you want.
*/
void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, const char *name);
#endif // _AKGL_ACTOR_H_