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:
22
src/actor.c
22
src/actor.c
@@ -174,13 +174,23 @@ akerr_ErrorContext *akgl_actor_update(akgl_Actor *obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Actor visible.
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param camera Camera rectangle used for visibility testing.
|
||||
* @param visible Output set to whether the actor intersects the camera.
|
||||
* @brief Decide whether an actor is worth drawing this frame.
|
||||
*
|
||||
* Two questions at once: is the actor on camera, and does it want to be drawn.
|
||||
* The camera test allows a sprite's own width and height on the near edges, so
|
||||
* an actor partly on screen still counts as visible rather than popping in once
|
||||
* its origin crosses the boundary.
|
||||
*
|
||||
* An actor with no sprite for its current state is reported as not visible
|
||||
* rather than as an error -- there is nothing to draw, which is an answer.
|
||||
*
|
||||
* @param obj The actor to test. Required, along with its `basechar`.
|
||||
* @param camera The visible rectangle in map coordinates. Required in practice;
|
||||
* not checked, and dereferenced once a sprite has been found.
|
||||
* @param visible Receives the verdict. Required; not checked. Written on the
|
||||
* no-sprite path as well as the ordinary ones.
|
||||
* @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 obj or `obj->basechar` is `NULL`.
|
||||
*/
|
||||
static akerr_ErrorContext *actor_visible(akgl_Actor *obj, SDL_FRect *camera, bool *visible)
|
||||
{
|
||||
|
||||
@@ -91,12 +91,28 @@ void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID regis
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Character load json state int from strings.
|
||||
* @param states JSON array of actor-state names.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @brief OR a JSON array of actor-state names together into one bitmask.
|
||||
*
|
||||
* This is what lets a character JSON say `["FACE_LEFT", "MOVING_LEFT"]` rather
|
||||
* than a number: each name is looked up in #AKGL_REGISTRY_ACTOR_STATE_STRINGS
|
||||
* and OR-ed in. A name that is not a known state is an error rather than being
|
||||
* skipped -- a typo in a state name would otherwise bind a sprite to the wrong
|
||||
* combination and show up as art that never appears.
|
||||
*
|
||||
* @param states JSON array of state-name strings. Required. An empty array is
|
||||
* legal and leaves @p dest as it was.
|
||||
* @param dest Receives the OR of every named bit. Required in practice, and
|
||||
* **not** checked -- the guard that should test it tests @p states
|
||||
* a second time instead, so a `NULL` here is a crash. It is OR-ed
|
||||
* into rather than assigned, so the caller must zero it first.
|
||||
* @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 states is `NULL`.
|
||||
* @throws AKERR_KEY If a name is not in the actor-state registry. The message
|
||||
* quotes it. Note that bits 11 and 12 are unreachable by name; see
|
||||
* akgl_registry_init_actor_state_strings.
|
||||
* @throws AKERR_TYPE If an array element is not a string.
|
||||
* @throws AKERR_OUTOFBOUNDS If the array is indexed past its end.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*/
|
||||
static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_t *states, int *dest)
|
||||
{
|
||||
@@ -123,11 +139,27 @@ static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Character load json inner.
|
||||
* @param json JSON object to parse.
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @brief Name a character and build its state-to-sprite map from a JSON document.
|
||||
*
|
||||
* The half of akgl_character_load_json that deals with identity and sprites; the
|
||||
* caller handles the numeric fields afterwards. Reads `name`, initializes the
|
||||
* character with it, then walks `sprite_mappings` binding each named sprite to
|
||||
* the bitmask its `state` array adds up to.
|
||||
*
|
||||
* Every sprite named must already be in #AKGL_REGISTRY_SPRITE.
|
||||
*
|
||||
* @param json The parsed character document. Required in practice; not checked,
|
||||
* and passed straight to accessors that report it.
|
||||
* @param obj The pooled character to fill in. Required, unchecked -- it is
|
||||
* handed to akgl_character_initialize, which does check it.
|
||||
* @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 a mapping names a sprite that is not registered.
|
||||
* The message names the character, the state, and the sprite.
|
||||
* @throws AKERR_KEY If `name`, `sprite_mappings`, or a mapping's `sprite` or
|
||||
* `state` is absent, or if a state name is unknown.
|
||||
* @throws AKERR_TYPE If one of those keys has the wrong JSON type.
|
||||
* @throws AKERR_OUTOFBOUNDS If an array is indexed past its end.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*/
|
||||
static akerr_ErrorContext *akgl_character_load_json_inner(json_t *json, akgl_Character *obj)
|
||||
{
|
||||
|
||||
@@ -152,11 +152,29 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gamepad handle button down.
|
||||
* @param appstate Application state supplied by SDL.
|
||||
* @param event SDL input event to process.
|
||||
* @brief Set the movement and facing bits on the "player" actor for a D-pad or arrow press.
|
||||
*
|
||||
* A whole-application shortcut rather than a control-map binding: it looks the
|
||||
* actor up by the literal name `"player"` in #AKGL_REGISTRY_ACTOR instead of
|
||||
* being told which actor to act on, so it only ever drives one. The
|
||||
* akgl_Actor_cmhf_* handlers are the general form.
|
||||
*
|
||||
* Facing follows movement unless the actor sets `movement_controls_face`, which
|
||||
* is how an actor that aims independently of the direction it walks opts out.
|
||||
*
|
||||
* Declared nowhere -- `controller.h` advertises this as
|
||||
* `akgl_controller_handle_button_down`, which does not exist. TODO.md, "Known
|
||||
* and still open" item 10.
|
||||
*
|
||||
* @param appstate Passed through from SDL. Required as a non-`NULL` token only;
|
||||
* never read.
|
||||
* @param event The button or key event. Required. Both the gamepad and
|
||||
* keyboard unions are read on every call, so the arm that does
|
||||
* not correspond to the event type is read as garbage -- which
|
||||
* works only because no real button and keycode pair collides.
|
||||
* @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 appstate or @p event is `NULL`, or if there is
|
||||
* no actor registered under the name "player".
|
||||
*/
|
||||
akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event)
|
||||
{
|
||||
@@ -210,11 +228,20 @@ akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gamepad handle button up.
|
||||
* @param appstate Application state supplied by SDL.
|
||||
* @param event SDL input event to process.
|
||||
* @brief Clear the movement bit on the "player" actor for a D-pad or arrow release, and reset its animation.
|
||||
*
|
||||
* The counterpart to gamepad_handle_button_down. It clears only the movement
|
||||
* bit, leaving the facing bits alone so the actor keeps looking the way it was
|
||||
* walking, and rewinds `curSpriteFrameId` to 0 so the next step starts from the
|
||||
* first frame of the walk cycle rather than wherever it stopped.
|
||||
*
|
||||
* Declared nowhere; see gamepad_handle_button_down.
|
||||
*
|
||||
* @param appstate Passed through from SDL. Required as a non-`NULL` token only.
|
||||
* @param event The button or key release event. 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 appstate or @p event is `NULL`, or if there is
|
||||
* no actor registered under the name "player".
|
||||
*/
|
||||
akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event)
|
||||
{
|
||||
@@ -256,11 +283,28 @@ akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gamepad handle added.
|
||||
* @param appstate Application state supplied by SDL.
|
||||
* @param event SDL input event to process.
|
||||
* @brief Open a gamepad that has just been plugged in, and log its mapping.
|
||||
*
|
||||
* SDL delivers no button events from an unopened gamepad, so a device that
|
||||
* appears mid-session has to be opened here the way akgl_controller_open_gamepads
|
||||
* opens the ones present at startup. A gamepad SDL has already opened is logged
|
||||
* and left alone.
|
||||
*
|
||||
* The mapping is logged because a controller with no entry in the database
|
||||
* produces no button events at all, and that is otherwise indistinguishable
|
||||
* from a broken binding.
|
||||
*
|
||||
* Declared nowhere; see gamepad_handle_button_down.
|
||||
*
|
||||
* @param appstate Passed through from SDL. Required as a non-`NULL` token only.
|
||||
* @param event The SDL_EVENT_GAMEPAD_ADDED event. Required. The joystick id
|
||||
* is read out of the `gbutton` arm rather than `gdevice`, which
|
||||
* works because the two share their `which` field.
|
||||
* @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 appstate or @p event is `NULL`.
|
||||
*
|
||||
* @note A failed `SDL_OpenGamepad` is logged rather than reported: this returns
|
||||
* success and the device stays silent.
|
||||
*/
|
||||
akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event)
|
||||
{
|
||||
@@ -293,11 +337,19 @@ akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gamepad handle removed.
|
||||
* @param appstate Application state supplied by SDL.
|
||||
* @param event SDL input event to process.
|
||||
* @brief Close a gamepad that has been unplugged.
|
||||
*
|
||||
* An unplugged device SDL still has open is a leaked handle, so this closes it.
|
||||
* A removal for a device that was never opened is logged and otherwise ignored.
|
||||
* Any control map still holding that `jsid` simply stops matching -- the map is
|
||||
* not torn down.
|
||||
*
|
||||
* Declared nowhere; see gamepad_handle_button_down.
|
||||
*
|
||||
* @param appstate Passed through from SDL. Required as a non-`NULL` token only.
|
||||
* @param event The SDL_EVENT_GAMEPAD_REMOVED event. 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 appstate or @p event is `NULL`.
|
||||
*/
|
||||
akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event)
|
||||
{
|
||||
|
||||
47
src/draw.c
47
src/draw.c
@@ -13,9 +13,9 @@
|
||||
|
||||
/** @brief One horizontal run of pixels the flood fill has still to examine. */
|
||||
typedef struct {
|
||||
int x1;
|
||||
int x2;
|
||||
int y;
|
||||
int x1; /**< Leftmost column of the run, inclusive. */
|
||||
int x2; /**< Rightmost column of the run, inclusive. */
|
||||
int y; /**< The row the run is on. */
|
||||
} FloodSpan;
|
||||
|
||||
/*
|
||||
@@ -59,6 +59,16 @@ void akgl_draw_background(int w, int h)
|
||||
*
|
||||
* @p previous is written before anything that can fail, so a caller may restore
|
||||
* it unconditionally from a CLEANUP block.
|
||||
*
|
||||
* @param self The backend. Assumed non-`NULL` with a live `sdl_renderer`;
|
||||
* every caller has already checked both.
|
||||
* @param color The colour to install.
|
||||
* @param previous Receives the colour that was in place. Assumed non-`NULL`.
|
||||
* Pre-filled with opaque black, so it is safe to restore from
|
||||
* even if the query below fails.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKGL_ERR_SDL If the current draw colour cannot be read or the new one
|
||||
* cannot be set. The message carries `SDL_GetError()`.
|
||||
*/
|
||||
static akerr_ErrorContext *push_draw_color(akgl_RenderBackend *self, SDL_Color color, SDL_Color *previous)
|
||||
{
|
||||
@@ -82,7 +92,18 @@ static akerr_ErrorContext *push_draw_color(akgl_RenderBackend *self, SDL_Color c
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/** @brief Put back the draw color push_draw_color() recorded. */
|
||||
/**
|
||||
* @brief Put back the draw color push_draw_color() recorded.
|
||||
*
|
||||
* Called from `CLEANUP` blocks under `IGNORE()`, so its error is logged rather
|
||||
* than propagated -- failing to restore a colour must not mask the failure the
|
||||
* cleanup is unwinding from.
|
||||
*
|
||||
* @param self The backend. Assumed non-`NULL` with a live `sdl_renderer`.
|
||||
* @param previous The colour push_draw_color() recorded. Assumed non-`NULL`.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKGL_ERR_SDL If the draw colour cannot be set.
|
||||
*/
|
||||
static akerr_ErrorContext *pop_draw_color(akgl_RenderBackend *self, SDL_Color *previous)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -112,6 +133,24 @@ static akerr_ErrorContext *pop_draw_color(akgl_RenderBackend *self, SDL_Color *p
|
||||
* Running out of stack leaves the region partially filled and reports
|
||||
* AKERR_OUTOFBOUNDS. There is no way to unwind a partial fill short of keeping
|
||||
* a copy of the whole surface, and the caller asked for a bounded operation.
|
||||
*
|
||||
* @param surface The pixels to fill, in SDL_PIXELFORMAT_RGBA32. Assumed
|
||||
* non-`NULL` and locked-or-lockless; the caller converts.
|
||||
* @param x Seed column. Assumed inside the surface -- akgl_draw_flood_fill
|
||||
* has already range-checked it.
|
||||
* @param y Seed row, likewise.
|
||||
* @param oldpixel The packed pixel value the region is made of. Everything else
|
||||
* is a boundary.
|
||||
* @param newpixel The packed pixel value to write. Must differ from @p oldpixel:
|
||||
* if they are equal the fill never terminates making progress,
|
||||
* which is why the caller checks that case first.
|
||||
* @param dirty Receives the bounding box of everything written, so the caller
|
||||
* can put back only the pixels that changed. Assumed non-`NULL`.
|
||||
* Left describing an empty box if nothing matched.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_OUTOFBOUNDS If the region needs more than
|
||||
* #AKGL_DRAW_MAX_FLOOD_SPANS pending spans. @p dirty is then not written
|
||||
* and the surface is partially filled.
|
||||
*/
|
||||
static akerr_ErrorContext *flood_region(SDL_Surface *surface, int x, int y, uint32_t oldpixel, uint32_t newpixel, SDL_Rect *dirty)
|
||||
{
|
||||
|
||||
156
src/game.c
156
src/game.c
@@ -190,10 +190,24 @@ void akgl_game_updateFPS()
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Serializes an actor name-to-pointer entry.
|
||||
* @param userdata Open output stream supplied by the caller.
|
||||
* @param props Actor registry being iterated.
|
||||
* @param name Actor registry key to serialize.
|
||||
* @brief Write one actor's name and save-time address to the name table.
|
||||
*
|
||||
* An `SDL_EnumerateProperties` callback. The name is written at a fixed
|
||||
* #AKGL_ACTOR_MAX_NAME_LENGTH bytes so the reader can walk the table without a
|
||||
* length prefix, and the pointer is written raw -- it is not a pointer the
|
||||
* loader will ever dereference, only a key it maps back to the object that has
|
||||
* the same name next run.
|
||||
*
|
||||
* @param userdata The open output stream, as a `FILE *`. Required.
|
||||
* @param props #AKGL_REGISTRY_ACTOR, supplied by SDL.
|
||||
* @param name The actor's registry key. Read for
|
||||
* #AKGL_ACTOR_MAX_NAME_LENGTH bytes regardless of its actual
|
||||
* length, so a shorter key is read past its end.
|
||||
*
|
||||
* @warning Ends in `FINISH_NORETURN`: an unhandled error here logs a stack trace
|
||||
* and then calls libakerror's unhandled-error handler, which by default
|
||||
* **exits the process**. A failed write during a save terminates the
|
||||
* game rather than returning an error.
|
||||
*/
|
||||
void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
@@ -211,10 +225,17 @@ void akgl_game_save_actorname_iterator(void *userdata, SDL_PropertiesID props, c
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game save spritename iterator.
|
||||
* @param userdata Caller data supplied to the SDL property iterator.
|
||||
* @param props SDL property collection being iterated.
|
||||
* @param name Registry key or human-readable object name.
|
||||
* @brief Write one sprite's name and save-time address to the name table.
|
||||
*
|
||||
* As akgl_game_save_actorname_iterator, but the name field is
|
||||
* #AKGL_SPRITE_MAX_NAME_LENGTH wide.
|
||||
*
|
||||
* @param userdata The open output stream, as a `FILE *`. Required.
|
||||
* @param props #AKGL_REGISTRY_SPRITE, supplied by SDL.
|
||||
* @param name The sprite's registry key, written at fixed width.
|
||||
*
|
||||
* @warning Terminates the process on an unhandled error. See
|
||||
* akgl_game_save_actorname_iterator.
|
||||
*/
|
||||
void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
@@ -232,10 +253,21 @@ void akgl_game_save_spritename_iterator(void *userdata, SDL_PropertiesID props,
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game save spritesheetname iterator.
|
||||
* @param userdata Caller data supplied to the SDL property iterator.
|
||||
* @param props SDL property collection being iterated.
|
||||
* @param name Registry key or human-readable object name.
|
||||
* @brief Write one spritesheet's name and save-time address to the name table.
|
||||
*
|
||||
* As akgl_game_save_actorname_iterator, but the name field is
|
||||
* #AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH wide -- the widest of the four, since
|
||||
* a spritesheet is keyed by its resolved path.
|
||||
*
|
||||
* @param userdata The open output stream, as a `FILE *`. Required.
|
||||
* @param props #AKGL_REGISTRY_SPRITESHEET, supplied by SDL.
|
||||
* @param name The spritesheet's registry key, written at fixed width.
|
||||
*
|
||||
* @warning Terminates the process on an unhandled error. See
|
||||
* akgl_game_save_actorname_iterator.
|
||||
* @note This is the table the loader disagrees with: it reads every table at
|
||||
* #AKGL_ACTOR_MAX_NAME_LENGTH, so a save containing any spritesheet cannot
|
||||
* be read back. TODO.md, "Known and still open" item 7.
|
||||
*/
|
||||
void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
@@ -253,10 +285,17 @@ void akgl_game_save_spritesheetname_iterator(void *userdata, SDL_PropertiesID pr
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game save charactername iterator.
|
||||
* @param userdata Caller data supplied to the SDL property iterator.
|
||||
* @param props SDL property collection being iterated.
|
||||
* @param name Registry key or human-readable object name.
|
||||
* @brief Write one character's name and save-time address to the name table.
|
||||
*
|
||||
* As akgl_game_save_actorname_iterator, but the name field is
|
||||
* #AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH wide.
|
||||
*
|
||||
* @param userdata The open output stream, as a `FILE *`. Required.
|
||||
* @param props #AKGL_REGISTRY_CHARACTER, supplied by SDL.
|
||||
* @param name The character's registry key, written at fixed width.
|
||||
*
|
||||
* @warning Terminates the process on an unhandled error. See
|
||||
* akgl_game_save_actorname_iterator.
|
||||
*/
|
||||
void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID props, const char *name)
|
||||
{
|
||||
@@ -274,10 +313,23 @@ void akgl_game_save_charactername_iterator(void *userdata, SDL_PropertiesID prop
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game save actors.
|
||||
* @param fp Open save-game stream.
|
||||
* @brief Write all four name-to-address tables, each with its terminator.
|
||||
*
|
||||
* Actors, sprites, spritesheets, characters, in that order -- which is the order
|
||||
* akgl_game_load reads them back in. Each table is the registry enumerated one
|
||||
* entry at a time, followed by a zeroed name field and a zeroed pointer that the
|
||||
* reader stops on.
|
||||
*
|
||||
* @param fp The open save-game stream. 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 fp is `NULL`.
|
||||
* @throws AKERR_IO On a stream error or a short write while emitting a
|
||||
* terminator.
|
||||
*
|
||||
* @note Only the terminators are written through the error-reporting path. The
|
||||
* entries themselves go through `SDL_EnumerateProperties`, whose callbacks
|
||||
* cannot report failure upward and instead terminate the process -- so a
|
||||
* write error mid-table never reaches this function's return value.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save_actors(FILE *fp)
|
||||
{
|
||||
@@ -352,14 +404,40 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_save(char *fpath)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game load objectnamemap.
|
||||
* @param fp Open save-game stream.
|
||||
* @param map Tilemap to inspect, draw, or modify.
|
||||
* @param namelength Fixed serialized name-field length.
|
||||
* @param ptrlength Serialized pointer-field length.
|
||||
* @param registry SDL property registry being queried.
|
||||
* @brief Read one name table back, building an old-address to current-object map.
|
||||
*
|
||||
* This is the mechanism that makes a save file portable across runs. Objects are
|
||||
* serialized carrying the addresses they had at save time; those addresses mean
|
||||
* nothing now, but the table says which *name* lived at each one, and the name
|
||||
* still resolves through the registry. So each entry becomes
|
||||
* `"0x7f..." -> current object`, and a deserialized pointer can be translated by
|
||||
* looking up its printed form.
|
||||
*
|
||||
* The keys are the old pointers rendered with `%p`, because SDL property sets
|
||||
* take string keys only.
|
||||
*
|
||||
* Reading stops at the terminator -- a zeroed name and a zeroed pointer.
|
||||
*
|
||||
* @param fp The open save-game stream, positioned at the start of a
|
||||
* table. Required.
|
||||
* @param map The property set to fill in. Required, and created by the
|
||||
* caller.
|
||||
* @param namelength Width of the table's name field, which must match what the
|
||||
* corresponding save iterator wrote. It also sizes a
|
||||
* variable-length array on the stack.
|
||||
* @param ptrlength Width of the table's pointer field, i.e. `sizeof` the
|
||||
* pointer type that table holds.
|
||||
* @param registry The registry to resolve names against -- #AKGL_REGISTRY_ACTOR
|
||||
* for the actor table, and so on.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_* Propagates an error reported by a delegated operation.
|
||||
* @throws AKERR_EOF If the stream ends before a terminator is found, which is
|
||||
* what a truncated save file looks like.
|
||||
* @throws AKERR_IO On a stream error.
|
||||
* @throws AKERR_NULLPOINTER If @p fp is `NULL`, by way of the read wrapper.
|
||||
*
|
||||
* @note A name in the table that is no longer in @p registry maps to `NULL`
|
||||
* rather than being reported: the entry is written with whatever
|
||||
* `SDL_GetPointerProperty` returned.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_PropertiesID map, int namelength, int ptrlength, SDL_PropertiesID registry)
|
||||
{
|
||||
@@ -406,14 +484,24 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_Pr
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Game load versioncmp.
|
||||
* @param versiontype Description of the version being compared.
|
||||
* @param newversion Version read from the save data.
|
||||
* @param curversion Version supported by the running library.
|
||||
* @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.
|
||||
* @throws AKERR_VALUE When the corresponding validation or operation fails.
|
||||
* @brief Refuse a save file unless its version matches the running one exactly.
|
||||
*
|
||||
* Both strings are parsed as semver and compared with `"="` -- exact equality,
|
||||
* not compatibility. That is deliberate and strict: a save file is a raw memory
|
||||
* image of `akgl_Game` and the object tables, so any change to a struct layout
|
||||
* invalidates it, and semver has no way to say "the layout did not move".
|
||||
*
|
||||
* @param versiontype What is being compared -- `"library"` or `"game"`. Used
|
||||
* only to build the message, but required, since a bare
|
||||
* "incompatible version" error would not say which.
|
||||
* @param newversion The version read out of the save file. Required.
|
||||
* @param curversion The version of the running program or library. Required.
|
||||
* @return `NULL` when the two match, otherwise an error context owned by the
|
||||
* caller.
|
||||
* @throws AKERR_NULLPOINTER If any of the three arguments is `NULL`.
|
||||
* @throws AKERR_VALUE If either string is not valid semver. The message says
|
||||
* which side it came from.
|
||||
* @throws AKERR_API If both parse but are not equal. The message quotes both.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype, char *newversion, char *curversion)
|
||||
{
|
||||
|
||||
29
src/sprite.c
29
src/sprite.c
@@ -41,12 +41,31 @@ akerr_ErrorContext *akgl_sprite_sheet_coords_for_frame(akgl_Sprite *self, SDL_FR
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sprite load json spritesheet.
|
||||
* @param json JSON object to parse.
|
||||
* @param sheet Spritesheet used by the sprite.
|
||||
* @param relative_path Base directory for relative asset references.
|
||||
* @brief Find or load the spritesheet a sprite definition names.
|
||||
*
|
||||
* Resolves the `spritesheet.filename` path relative to the sprite definition's
|
||||
* own directory, then looks that resolved path up in #AKGL_REGISTRY_SPRITESHEET.
|
||||
* A hit is reused as is -- which is what keeps ten sprites cut from one image
|
||||
* down to one texture -- and a miss claims a sheet from the pool and loads it,
|
||||
* reading `frame_width` and `frame_height` only in that case.
|
||||
*
|
||||
* @param json The parsed sprite document, whose `spritesheet` object
|
||||
* this reads. Required in practice; not checked.
|
||||
* @param sheet Receives the sheet, whether found or freshly loaded.
|
||||
* Required; not checked. On the reuse path the sheet's
|
||||
* reference count is *not* incremented, so the sprite
|
||||
* borrows it.
|
||||
* @param relative_path Directory to resolve the image path against -- the
|
||||
* directory holding the sprite JSON. Required.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_* Propagates an error reported by a delegated operation.
|
||||
* @throws AKERR_KEY If the document has no `spritesheet` object, or that object
|
||||
* has no `filename` -- or, on the load path, no `frame_width` or
|
||||
* `frame_height`.
|
||||
* @throws AKERR_TYPE If one of those has the wrong JSON type.
|
||||
* @throws AKERR_OUTOFBOUNDS If the joined path is too long for a pooled string.
|
||||
* @throws ENOENT If the image path does not exist.
|
||||
* @throws AKGL_ERR_SDL If the image cannot be decoded.
|
||||
* @throws AKGL_ERR_HEAP If the spritesheet or string pool is exhausted.
|
||||
*/
|
||||
static akerr_ErrorContext *akgl_sprite_load_json_spritesheet(json_t *json, akgl_SpriteSheet **sheet, char *relative_path)
|
||||
{
|
||||
|
||||
157
src/tilemap.c
157
src/tilemap.c
@@ -88,12 +88,21 @@ akerr_ErrorContext *akgl_get_json_properties_integer(json_t *obj, char *key, int
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get json properties number.
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param key JSON object key or property name to locate.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @brief Read a Tiled custom property declared as `number`.
|
||||
*
|
||||
* Tiled writes `float` for a floating-point property, so this matches nothing
|
||||
* Tiled produces today -- akgl_get_json_properties_float is the one the loader
|
||||
* uses. Kept because a hand-written or older map may still say `number`.
|
||||
*
|
||||
* @param obj The Tiled object whose `properties` array to search. Required.
|
||||
* @param key The property name. Required.
|
||||
* @param dest Receives the value as a `float`. Not written on any failure path.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_* Propagates an error reported by a delegated operation.
|
||||
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
|
||||
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
|
||||
* @throws AKERR_TYPE If the property is not declared `number`, or its `value` is
|
||||
* not numeric.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, float *dest)
|
||||
{
|
||||
@@ -106,12 +115,20 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get json properties float.
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param key JSON object key or property name to locate.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @brief Read a Tiled custom property declared as `float`.
|
||||
*
|
||||
* The spelling Tiled actually writes for a floating-point property. This is how
|
||||
* the `scale` on a perspective marker is read.
|
||||
*
|
||||
* @param obj The Tiled object whose `properties` array to search. Required.
|
||||
* @param key The property name. Required.
|
||||
* @param dest Receives the value. Not written on any failure path.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_* Propagates an error reported by a delegated operation.
|
||||
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
|
||||
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
|
||||
* @throws AKERR_TYPE If the property is not declared `float`, or its `value` is
|
||||
* not numeric.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float *dest)
|
||||
{
|
||||
@@ -124,12 +141,22 @@ akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get json properties double.
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @param key JSON object key or property name to locate.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @brief Read a Tiled custom property declared as `float`, at full precision.
|
||||
*
|
||||
* Same declared type as akgl_get_json_properties_float, but writing a `double`.
|
||||
* The map's physics constants are `double`, which is the reason both exist.
|
||||
*
|
||||
* @param obj The Tiled object whose `properties` array to search. Required.
|
||||
* @param key The property name. Required.
|
||||
* @param dest Receives the value. Not written on any failure path.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_* Propagates an error reported by a delegated operation.
|
||||
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
|
||||
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
|
||||
* akgl_tilemap_load_physics passes this status to
|
||||
* akgl_get_json_with_default, which turns it into a default of 0.0.
|
||||
* @throws AKERR_TYPE If the property is not declared `float`, or its `value` is
|
||||
* not numeric.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_get_json_properties_double(json_t *obj, char *key, double *dest)
|
||||
{
|
||||
@@ -262,13 +289,39 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root,
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tilemap load layer object actor.
|
||||
* @param curobj Tilemap object being populated.
|
||||
* @param layerdatavalue JSON object describing the layer entry.
|
||||
* @param layerid Zero-based tilemap layer index.
|
||||
* @param dirname Directory containing paths referenced by the document.
|
||||
* @brief Turn one Tiled object into a live actor, creating it if it is not already registered.
|
||||
*
|
||||
* The object's name is the actor's registry key, which is what lets the same
|
||||
* actor be placed by more than one object: the first placement creates it and
|
||||
* binds the character named in the object's `character` property, and every
|
||||
* later one just takes another reference. Either way the object's position,
|
||||
* visibility, and layer are copied onto the actor and the object keeps a
|
||||
* pointer to it.
|
||||
*
|
||||
* Not declared in tilemap.h -- reachable only through
|
||||
* akgl_tilemap_load_layer_objects.
|
||||
*
|
||||
* @param curobj The tilemap object, with its `name`, `x`, `y`, and
|
||||
* `visible` already read from JSON. Required, unchecked,
|
||||
* dereferenced at once.
|
||||
* @param layerdatavalue The object's JSON, for the custom properties --
|
||||
* `character` and `state` -- that are not part of Tiled's
|
||||
* own object fields. Required.
|
||||
* @param layerid The layer this object sits on, copied onto the actor.
|
||||
* @param dirname Directory to resolve paths against. Accepted for
|
||||
* signature consistency; nothing here uses it.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_KEY When the corresponding validation or operation fails.
|
||||
* @throws AKERR_KEY If the object's name is empty -- an actor has to be
|
||||
* addressable -- if the `character` or `state` property is absent, or if
|
||||
* the named character is not in #AKGL_REGISTRY_CHARACTER.
|
||||
* @throws AKERR_TYPE If `character` is not declared `string` or `state` not
|
||||
* declared `int`.
|
||||
* @throws AKERR_NULLPOINTER If akgl_actor_initialize refuses the name.
|
||||
* @throws AKGL_ERR_HEAP If the actor or string pool is exhausted.
|
||||
*
|
||||
* @note An existing actor's `character` and position are overwritten by each
|
||||
* later placement, so two objects naming one actor leave it wherever the
|
||||
* last one put it rather than producing two.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *curobj, json_t *layerdatavalue, int layerid, akgl_String *dirname)
|
||||
{
|
||||
@@ -392,14 +445,32 @@ akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *roo
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tilemap load layer image.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @param root Base directory used to resolve the path.
|
||||
* @param layerid Zero-based tilemap layer index.
|
||||
* @param dirname Directory containing paths referenced by the document.
|
||||
* @brief Load one image layer: upload its image as a single texture.
|
||||
*
|
||||
* An image layer is one picture rather than a grid, which is what a parallax
|
||||
* backdrop wants. The layer's `width` and `height` are taken from the loaded
|
||||
* texture rather than from the JSON, so they are always the true pixel size.
|
||||
*
|
||||
* Not declared in tilemap.h -- reachable only through akgl_tilemap_load_layers.
|
||||
*
|
||||
* @param dest The map to load into. Required.
|
||||
* @param root The layer's JSON object. Required.
|
||||
* @param layerid Which layer slot to fill. Not bounds-checked.
|
||||
* @param dirname Directory to resolve the layer's `image` path against.
|
||||
* Required. Joined with a `/` rather than run through
|
||||
* akgl_path_relative, so unlike a tileset image this path is not
|
||||
* canonicalized and an absolute one will not work.
|
||||
* @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_SDL When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p dest, @p root, or @p dirname is `NULL`.
|
||||
* @throws AKERR_KEY If the layer has no `image`.
|
||||
* @throws AKERR_TYPE If `image` is not a string.
|
||||
* @throws AKGL_ERR_SDL If the image cannot be loaded. The message carries
|
||||
* `SDL_GetError()`.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*
|
||||
* @note The joined path is truncated at
|
||||
* #AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE without complaint, so an
|
||||
* over-long path fails as a missing file rather than as a length error.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname)
|
||||
{
|
||||
@@ -488,11 +559,31 @@ akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, a
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Tilemap load physics.
|
||||
* @param dest Output destination populated by the function.
|
||||
* @param root Base directory used to resolve the path.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_KEY When the corresponding validation or operation fails.
|
||||
* @brief Give the map its own physics backend, if it asked for one.
|
||||
*
|
||||
* A map with a `physics.model` custom property gets its own backend, configured
|
||||
* from `physics.gravity.x`/`.y`/`.z` and `physics.drag.x`/`.y`/`.z`, and
|
||||
* `use_own_physics` is set so a caller knows to simulate through it. Each
|
||||
* constant defaults to 0.0 when absent, so a map can name a model and override
|
||||
* only what it cares about.
|
||||
*
|
||||
* Absence is the normal case at every level: a map with no properties at all, or
|
||||
* with properties but no `physics.model`, uses the game's global backend and
|
||||
* this returns success. That is why the AKERR_KEY handlers here are not error
|
||||
* paths -- they are the "map did not ask" path.
|
||||
*
|
||||
* Not declared in tilemap.h -- reachable only through akgl_tilemap_load.
|
||||
*
|
||||
* @param dest The map to configure. Required in practice; not checked here,
|
||||
* since akgl_tilemap_load has already done so.
|
||||
* @param root The map's root JSON object. Required, likewise.
|
||||
* @return `NULL` on success -- including when the map declared no physics at
|
||||
* all -- otherwise an error context owned by the caller.
|
||||
* @throws AKERR_KEY If `physics.model` names a backend that does not exist.
|
||||
* @throws AKERR_TYPE If `physics.model` is not declared `string`, or one of the
|
||||
* six constants is not declared `float`.
|
||||
* @throws AKERR_NULLPOINTER If akgl_physics_factory refuses its arguments.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root)
|
||||
{
|
||||
|
||||
58
src/util.c
58
src/util.c
@@ -21,13 +21,34 @@
|
||||
#include <akstdlib.h>
|
||||
|
||||
/**
|
||||
* @brief Path relative root.
|
||||
* @param root Base directory used to resolve the path.
|
||||
* @param path Path to resolve or transform.
|
||||
* @param dst Output destination populated by the function.
|
||||
* @brief Resolve @p path against @p root and write the absolute result to @p dst.
|
||||
*
|
||||
* The second half of akgl_path_relative: joins the two with a `/` and resolves
|
||||
* the join, so symlinks and `..` are folded out and the result is absolute. It
|
||||
* is the fallback, reached only once resolving @p path on its own has failed.
|
||||
*
|
||||
* Not declared in util.h -- it is reachable only through akgl_path_relative.
|
||||
*
|
||||
* @param root Directory to resolve against, normally `dirname` of the file that
|
||||
* named @p path. Required. A trailing `/` is harmless; the join adds
|
||||
* one unconditionally and `realpath` collapses the double.
|
||||
* @param path Relative path to resolve. Required. An absolute @p path still gets
|
||||
* @p root pasted in front of it, which will not exist -- so callers
|
||||
* must not send absolute paths down this branch.
|
||||
* @param dst Receives the resolved absolute path. Required, and must already be
|
||||
* a claimed pool string.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When the corresponding validation or operation fails.
|
||||
* @throws AKERR_OUTOFBOUNDS When the corresponding validation or operation fails.
|
||||
* @throws AKERR_NULLPOINTER If @p root, @p path, or @p dst is `NULL`.
|
||||
* @throws AKERR_OUTOFBOUNDS If `root + path` is at least #AKGL_MAX_STRING_LENGTH
|
||||
* bytes. The message reports both the combined length and the limit.
|
||||
* @throws ENOENT If the joined path does not exist. Any other `errno`
|
||||
* `realpath(3)` raises -- EACCES, ELOOP, ENOTDIR -- propagates likewise.
|
||||
* @throws AKGL_ERR_HEAP If the string pool cannot supply the two scratch buffers.
|
||||
*
|
||||
* @note The AKERR_OUTOFBOUNDS check uses `FAIL_RETURN` from inside the `ATTEMPT`
|
||||
* block, which returns past `CLEANUP` -- so on that one path the two
|
||||
* scratch strings are never released. This is exactly the hazard AGENTS.md
|
||||
* warns about; it wants a `FAIL_BREAK`.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_path_relative_root(char *root, char *path, akgl_String *dst)
|
||||
{
|
||||
@@ -102,12 +123,27 @@ akerr_ErrorContext *akgl_path_relative(char *root, char *path, akgl_String *dst)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Path relative from.
|
||||
* @param path Path to resolve or transform.
|
||||
* @param from Base path from which the result is made relative.
|
||||
* @param dst Output destination populated by the function.
|
||||
* @brief Intended to resolve @p path against the directory holding @p from. Unfinished.
|
||||
*
|
||||
* The shape is there -- resolve @p from, take its `dirname` -- but the body
|
||||
* stops before it does anything with @p path, and `*dst` is never written. Not
|
||||
* declared in util.h, and nothing in the tree calls it.
|
||||
*
|
||||
* @param path The path to resolve. Required, and currently ignored past the
|
||||
* `NULL` check.
|
||||
* @param from A file whose directory @p path should be taken as relative to.
|
||||
* Required. Must exist -- it is resolved with `realpath(3)`.
|
||||
* @param dst Intended to receive the resolved path. Never written. Note the
|
||||
* double indirection, which disagrees with the rest of the
|
||||
* `akgl_path_relative*` family; see TODO.md item 40.
|
||||
* @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 path or @p from is `NULL`.
|
||||
* @throws ENOENT If @p from does not exist, along with any other `errno`
|
||||
* `realpath(3)` raises.
|
||||
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
||||
*
|
||||
* @warning Known defect: the scratch string it claims is never released, so 256
|
||||
* calls exhaust the string pool. TODO.md, "Known and still open" item 4.
|
||||
*/
|
||||
akerr_ErrorContext *akgl_path_relative_from(char *path, char *from, akgl_String **dst)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user