Compare commits
11 Commits
coverage-t
...
5f03475e0f
| Author | SHA1 | Date | |
|---|---|---|---|
|
5f03475e0f
|
|||
|
6dfe7487ae
|
|||
|
c2b16d3c18
|
|||
|
2be9831c0c
|
|||
|
c5a7b6053d
|
|||
|
9b124f2e27
|
|||
|
b014eb2360
|
|||
|
e423f9594e
|
|||
|
772f960865
|
|||
|
28bde4176d
|
|||
|
8d21d5c7dd
|
16
.dir-locals.el
Normal file
16
.dir-locals.el
Normal file
@@ -0,0 +1,16 @@
|
||||
;;; Directory Local Variables -*- no-byte-compile: t -*-
|
||||
;;; See AGENTS.md -> "Coding Style" for the rationale.
|
||||
;;;
|
||||
;;; The canonical style is cc-mode "stroustrup" with tabs enabled: 4 columns per
|
||||
;;; level, tabs 8 columns wide, so depth 1 is four spaces, depth 2 is one tab,
|
||||
;;; depth 3 is a tab plus four spaces. A correctly formatted file is a fixed
|
||||
;;; point of `indent-region' under these settings.
|
||||
;;;
|
||||
;;; Reindent a whole file with C-x h C-M-\, or the tree with
|
||||
;;; `scripts/reindent.sh'.
|
||||
|
||||
((c-mode . ((c-file-style . "stroustrup")
|
||||
(indent-tabs-mode . t)
|
||||
(tab-width . 8)
|
||||
(fill-column . 100)
|
||||
(require-final-newline . t))))
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,3 +1,11 @@
|
||||
./build/*
|
||||
# A leading ./ is not a valid gitignore pattern, so "./build/*" matched nothing
|
||||
# and every out-of-tree build tree showed up as untracked. This also covers the
|
||||
# instrumented trees scripts/coverage.py and the AKGL_COVERAGE option create.
|
||||
build*/
|
||||
.aider*
|
||||
*~
|
||||
|
||||
# Generated by configure_file into the build tree. include/ precedes the build
|
||||
# tree on the include path, so a stray copy here would silently shadow the real
|
||||
# one and pin every consumer to whatever version it was generated at.
|
||||
include/akgl/version.h
|
||||
|
||||
259
AGENTS.md
259
AGENTS.md
@@ -2,7 +2,43 @@
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
This is a C library intended to support the development of video games. Public C headers live in `include/akgl/`; keep declarations there aligned with their implementations in `src/`. Tests are standalone C programs under `tests/`, with JSON, image, and map fixtures in `tests/assets/`. The `util/` directory contains the `charviewer` utility and its sample assets. Third-party and companion libraries are vendored in `deps/`. Treat `build/`, generated `akgl.pc` files, and `include/akgl/SDL_GameControllerDB.h` as build outputs rather than hand-maintained source.
|
||||
This is a C library intended to support the development of video games. Public C headers live in `include/akgl/`; keep declarations there aligned with their implementations in `src/`. Tests are standalone C programs under `tests/`, with JSON, image, and map fixtures in `tests/assets/`. The `util/` directory contains the `charviewer` utility and its sample assets. Third-party and companion libraries are vendored in `deps/`. Treat `build/` and generated `akgl.pc` files as build outputs rather than hand-maintained source; `include/akgl/SDL_GameControllerDB.h` is generated too, but is tracked on purpose — see below.
|
||||
|
||||
## Generated and Vendored Sources
|
||||
|
||||
### `include/akgl/SDL_GameControllerDB.h` is generated, and is tracked deliberately
|
||||
|
||||
`mkcontrollermappings.sh` regenerates this header by fetching the community
|
||||
controller database from `raw.githubusercontent.com`. **It is committed to the
|
||||
repository on purpose**: it is the offline fallback that keeps the library
|
||||
buildable if upstream is renamed, rate-limited, taken down, or simply
|
||||
unreachable from the build machine. Do not delete it, do not add it to
|
||||
`.gitignore`, and do not "clean up" the fact that a generated file is tracked.
|
||||
|
||||
Working rules:
|
||||
|
||||
- **Never hand-edit it.** It is machine-written; changes belong in
|
||||
`mkcontrollermappings.sh`.
|
||||
- **Do not commit incidental regeneration churn.** The build regenerates the
|
||||
header on every run, and the script stamps `$(date)` into a comment, so an
|
||||
ordinary build leaves the file modified with nothing of substance changed.
|
||||
Revert that before committing: `git checkout -- include/akgl/SDL_GameControllerDB.h`.
|
||||
- **Update it as a deliberate, standalone commit** when you actually want newer
|
||||
mappings, so the diff is reviewable and bisectable.
|
||||
- **Never commit a copy with `AKGL_SDL_GAMECONTROLLER_DB_LEN 0`.** That is the
|
||||
signature of a failed fetch, not an empty upstream — see the defect note
|
||||
below. Check the constant before staging this file.
|
||||
- Formatting tooling ignores it: `scripts/reindent.sh` and the pre-commit hook
|
||||
both skip it.
|
||||
|
||||
> **Known defect (tracked in `TODO.md`).** The safety net is currently
|
||||
> self-defeating. `mkcontrollermappings.sh` has no `set -e` and does not check
|
||||
> `curl`'s exit status, so when the fetch fails it writes a header with
|
||||
> `AKGL_SDL_GAMECONTROLLER_DB_LEN 0` and an empty array **over the good tracked
|
||||
> copy, and exits 0**. Because CMake re-runs the generator on every build, an
|
||||
> offline build silently destroys the very fallback the file exists to provide.
|
||||
> Until that is fixed, treat a dirty `SDL_GameControllerDB.h` after a build as
|
||||
> suspect and check the length constant before committing it.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
@@ -30,9 +66,226 @@ Run mutation testing with `cmake --build build --target mutation`. For a quick s
|
||||
|
||||
Generate HTML and Cobertura coverage reports with `cmake -S . -B build-coverage -DAKGL_COVERAGE=ON -DCMAKE_BUILD_TYPE=Debug`, then build and run CTest. Reports are written to `build-coverage/coverage/`; this mode requires `gcovr` and GCC or Clang.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
## Coding Style
|
||||
|
||||
Follow the surrounding C style: braces on the next line for function bodies, spaces inside control-flow parentheses, and short, focused functions. Preserve the indentation of the file being edited; older files contain both spaces and tabs. Public symbols use the `akgl_` prefix, public types use `akgl_TypeName`, and constants/macros use `AKGL_UPPER_SNAKE_CASE`. Name matching header/source pairs by feature, such as `include/akgl/sprite.h` and `src/sprite.c`. No repository-wide formatter or linter is configured, so avoid unrelated formatting churn.
|
||||
The canonical style is Emacs `cc-mode` **`stroustrup`**, with tabs enabled. This
|
||||
is not advisory — new and edited code must match what `cc-mode` produces, so that
|
||||
reindenting a region never shows up as a diff.
|
||||
|
||||
### Emacs setup
|
||||
|
||||
`.dir-locals.el` in the repository root already applies the style to every
|
||||
`c-mode` buffer, so nothing needs configuring by hand:
|
||||
|
||||
```elisp
|
||||
((c-mode . ((c-file-style . "stroustrup")
|
||||
(indent-tabs-mode . t)
|
||||
(tab-width . 8)
|
||||
(fill-column . 100)
|
||||
(require-final-newline . t))))
|
||||
```
|
||||
|
||||
Interactively: `C-x h C-M-\` (`mark-whole-buffer` + `indent-region`) reindents
|
||||
the buffer. A correctly formatted file is a fixed point of that operation —
|
||||
reindenting must produce no change.
|
||||
|
||||
### Reindenting from the command line
|
||||
|
||||
```sh
|
||||
scripts/reindent.sh # reindent every tracked C source in place
|
||||
scripts/reindent.sh src/tilemap.c # reindent only the named files
|
||||
scripts/reindent.sh --check # list non-conforming files, change nothing
|
||||
```
|
||||
|
||||
`--check` exits 1 when something needs reindenting and 2 if Emacs is missing, so
|
||||
it is usable from CI. The script drives Emacs in batch mode through
|
||||
`scripts/reindent.el`, which reindents, normalises leading whitespace to the
|
||||
canonical tab/space mix, strips trailing whitespace, and ensures a final
|
||||
newline. `include/akgl/SDL_GameControllerDB.h` is generated and always skipped.
|
||||
|
||||
Note that `reindent.el` deliberately does **not** use Emacs' `tabify`: that
|
||||
function's `tabify-regexp` is `" [ \t]+"`, which is not anchored to the start of
|
||||
a line, so it rewrites runs of spaces anywhere and destroys the hand-aligned
|
||||
value columns in the bit-flag tables in `actor.h` and `iterator.h`. Only leading
|
||||
whitespace is ever rewritten.
|
||||
|
||||
### The pre-commit hook
|
||||
|
||||
`scripts/hooks/pre-commit` reindents staged C sources before the commit is
|
||||
written. Enable it once per clone:
|
||||
|
||||
```sh
|
||||
git config core.hooksPath scripts/hooks
|
||||
```
|
||||
|
||||
It inspects the *staged* content rather than the working tree, so what lands in
|
||||
the commit is what was checked. When a file needs reindenting it is fixed in the
|
||||
working tree and re-staged — but only if the index and working tree agree for
|
||||
that file. If they differ, re-staging would sweep unstaged work into the commit,
|
||||
so the hook stops and tells you to reindent and stage it yourself. It steps
|
||||
aside during a merge, and warns rather than blocking if Emacs is unavailable.
|
||||
`git commit --no-verify` bypasses it.
|
||||
|
||||
For reference, `cc-mode` defines the style as:
|
||||
|
||||
```elisp
|
||||
("stroustrup"
|
||||
(c-basic-offset . 4)
|
||||
(c-comment-only-line-offset . 0)
|
||||
(c-offsets-alist . ((statement-block-intro . +)
|
||||
(substatement-open . 0)
|
||||
(substatement-label . 0)
|
||||
(label . 0)
|
||||
(statement-cont . +))))
|
||||
```
|
||||
|
||||
### Indentation and whitespace
|
||||
|
||||
- **4 columns per level** (`c-basic-offset` 4).
|
||||
- **Tabs are 8 columns wide and are used for indentation** (`indent-tabs-mode`
|
||||
`t`, `tab-width` 8). Emacs emits the largest possible run of tabs and pads the
|
||||
remainder with spaces, which produces this ladder. Editors that expand tabs, or
|
||||
that assume a 4-column tab, will silently corrupt it:
|
||||
|
||||
| Depth | Columns | Bytes |
|
||||
|---|---|---|
|
||||
| 1 | 4 | 4 spaces |
|
||||
| 2 | 8 | `TAB` |
|
||||
| 3 | 12 | `TAB` + 4 spaces |
|
||||
| 4 | 16 | `TAB` `TAB` |
|
||||
| 5 | 20 | `TAB` `TAB` + 4 spaces |
|
||||
|
||||
- No trailing whitespace. Files end with a single newline.
|
||||
- `case` labels sit at the same column as their `switch` (`label . 0`).
|
||||
- Continuation lines of a wrapped expression indent one level (`statement-cont . +`).
|
||||
|
||||
Most of `src/` already conforms. The known non-conforming files are
|
||||
`src/json_helpers.c`, `src/util.c` (from `akgl_rectangle_points` onward),
|
||||
`src/assets.c`, `src/staticstring.c`, parts of `src/actor.c`, and the headers
|
||||
`include/akgl/util.h` and `include/akgl/staticstring.h` — all of which use a
|
||||
2-column offset. Convert a file to the canonical style in its own commit, not
|
||||
mixed into a behavioral change.
|
||||
|
||||
### Braces
|
||||
|
||||
- **Function bodies open on their own line, in column 0.**
|
||||
- **Control statements keep the brace on the same line**, one space before it.
|
||||
- **`else`, `else if`, and `while` of a `do`/`while` stay on the closing-brace
|
||||
line**: `} else {`, not a bare `else` on the next line. Note that this is a
|
||||
house convention rather than something `cc-mode` enforces — the `stroustrup`
|
||||
style governs indentation only and will not move a brace or an `else` for you.
|
||||
- **Always brace, even single-statement bodies.**
|
||||
|
||||
```c
|
||||
akerr_ErrorContext *akgl_actor_update(akgl_Actor *obj)
|
||||
{
|
||||
if ( obj->curSpriteFrameId == 0 ) {
|
||||
obj->curSpriteReversing = false;
|
||||
} else if ( obj->parent != NULL ) {
|
||||
obj->curSpriteFrameId -= 1;
|
||||
} else {
|
||||
obj->curSpriteFrameId += 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Spacing
|
||||
|
||||
- **Spaces inside control-flow parentheses**: `if ( x == y ) {`, `while ( done == false ) {`,
|
||||
`for ( i = 0; i < len; i++ ) {`. This is the dominant existing convention
|
||||
(140 sites to 7) and `cc-mode` will not add or remove it.
|
||||
- No space between a function name and its argument list, at both call and
|
||||
definition sites, and no padding inside call parentheses: `SDL_Log("x %d", n)`.
|
||||
- Binary operators and assignment are surrounded by single spaces; unary
|
||||
operators are not separated from their operand.
|
||||
- **The pointer `*` binds to the identifier**, not the type: `char *name`,
|
||||
`akgl_Actor **dest`. Never `char* name`.
|
||||
- When a call is too long for one line, put each argument on its own line
|
||||
indented one level past the callee, with the closing `)` on its own line. This
|
||||
is the established shape for the `FAIL_*` and `CATCH` macros:
|
||||
|
||||
```c
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_SetPointerProperty(AKGL_REGISTRY_ACTOR, name, (void *)obj),
|
||||
AKERR_KEY,
|
||||
"Unable to add actor to registry"
|
||||
);
|
||||
```
|
||||
|
||||
### No repository-wide formatter
|
||||
|
||||
There is no `clang-format` or linter wired into the build, and `cc-mode`'s
|
||||
indentation engine is not exactly reproducible by `clang-format`. Do not
|
||||
introduce one without discussion, and do not reformat code you are not otherwise
|
||||
changing — unrelated whitespace churn makes review harder and is the reason the
|
||||
style drifted in the first place.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- **Public functions**: `akgl_<subsystem>_<verb>`, all lower snake_case —
|
||||
`akgl_actor_set_character`, `akgl_heap_next_string`. No camelCase, and never
|
||||
embed a type name (`akgl_Actor_cmhf_left_on` is wrong; it should be
|
||||
`akgl_actor_cmhf_left_on`).
|
||||
- **Public types**: `akgl_TypeName` — `akgl_Actor`, `akgl_SpriteSheet`,
|
||||
`akgl_PhysicsBackend`. Every type exported from a header takes the prefix;
|
||||
bare names like `point` and `RectanglePoints` are defects, not precedent.
|
||||
- **Constants and macros**: `AKGL_UPPER_SNAKE_CASE`. A constant belongs to the
|
||||
subsystem it describes: a character limit is `AKGL_CHARACTER_MAX_*`, not
|
||||
`AKGL_SPRITE_MAX_CHARACTER_*`. Name a constant for what its value *is* — a
|
||||
nanoseconds-per-millisecond scale factor is `AKGL_TIME_ONEMS_NS`.
|
||||
- **Exported globals** take the `akgl_` prefix like any other public symbol. Do
|
||||
not add bare names (`renderer`, `camera`, `window`), leading-underscore names
|
||||
(reserved at file scope), or SCREAMING_SNAKE names for mutable objects —
|
||||
`AKGL_UPPER_SNAKE_CASE` is for constants.
|
||||
- **`static` helpers drop the `akgl_` prefix**, which exists only to avoid
|
||||
external collisions.
|
||||
- **Include guards**: `_AKGL_<FILE>_H_`, matching the file name. Guard names
|
||||
without the project prefix risk colliding with system headers.
|
||||
- **Parameter names must match between the declaration and the definition** —
|
||||
Doxygen publishes the header spelling. Conventional names: `dest` for an
|
||||
output parameter (not `dst`), `self` for a backend receiver, `obj` for the
|
||||
instance being initialized or inspected, `e` for an incoming error context to
|
||||
be inspected.
|
||||
- **The local error context is named `errctx`** (92 sites to 45). New code uses
|
||||
`errctx`; convert `e` when you touch a function for other reasons.
|
||||
- **Header/source pairs are named by feature**: `include/akgl/sprite.h` and
|
||||
`src/sprite.c`.
|
||||
|
||||
## Error-Handling Protocol
|
||||
|
||||
The `akerror` control-flow macros have a shape that must be followed exactly,
|
||||
because the failure mode is silent.
|
||||
|
||||
- Inside an `ATTEMPT` block use the **`_BREAK`** variants (`FAIL_ZERO_BREAK`,
|
||||
`FAIL_NONZERO_BREAK`, `FAIL_BREAK`) and `CATCH`. Outside it use the
|
||||
**`_RETURN`** variants.
|
||||
- **Never use a `*_RETURN` macro inside an `ATTEMPT` block.** It returns past the
|
||||
`CLEANUP` block, so every release, `fclose`, and free in `CLEANUP` is skipped.
|
||||
This has already caused a heap-string leak on the success path of
|
||||
`akgl_get_json_tilemap_property`.
|
||||
- `CLEANUP` must precede `PROCESS`. Transposing them moves the cleanup body into
|
||||
the `PROCESS` switch, where it runs only when an error context exists.
|
||||
- `CATCH` reports failure by `break`ing, which binds to the innermost enclosing
|
||||
loop or `switch`. A `CATCH` written directly inside a `while` exits the loop
|
||||
rather than the function — put the `ATTEMPT` block inside the loop.
|
||||
- Validate every pointer parameter before dereferencing it, including the ones a
|
||||
sibling function happens not to check.
|
||||
|
||||
## API Surface
|
||||
|
||||
Every function with external linkage must be declared in a header, and every
|
||||
declaration must have a definition. A non-`static` function that appears in no
|
||||
header is still in the ABI but is unreachable by callers; a declaration with no
|
||||
definition is a link error for anyone compiling against the header alone. If a
|
||||
function is exposed only so tests can reach it, declare it under the existing
|
||||
"part of the internal API" comment block in the relevant header.
|
||||
|
||||
Headers must be self-contained: include what you use, so that a translation unit
|
||||
including a single `akgl` header compiles without a particular include order.
|
||||
Within the project use the angled form, `#include <akgl/sibling.h>`.
|
||||
|
||||
Declare no-argument functions as `(void)`, not `()`.
|
||||
|
||||
## Rules
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(akgl LANGUAGES C)
|
||||
# The single source of truth for the library version. It drives the generated
|
||||
# include/akgl/version.h, the shared library's VERSION and SOVERSION, and the
|
||||
# Version field in akgl.pc. Bump it here and nowhere else.
|
||||
project(akgl VERSION 0.1.0 LANGUAGES C)
|
||||
|
||||
include(CTest)
|
||||
option(AKGL_COVERAGE "Instrument libakgl and generate coverage reports with CTest" OFF)
|
||||
@@ -39,10 +42,8 @@ add_subdirectory(deps/SDL_image EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(deps/SDL_mixer EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(deps/SDL_ttf EXCLUDE_FROM_ALL)
|
||||
|
||||
# Reserve error-name table entries for libakgl-specific status codes.
|
||||
target_compile_definitions(akerror PUBLIC
|
||||
AKERR_MAX_ERR_VALUE=256
|
||||
)
|
||||
# libakerror 1.0.0 sizes its own status-name registry; consumers no longer do.
|
||||
# libakgl claims its status codes at runtime in akgl_heap_init() instead.
|
||||
|
||||
set(AKGL_SUPPRESS_DEPENDENCY_TESTS FALSE)
|
||||
|
||||
@@ -62,11 +63,19 @@ else()
|
||||
if(NOT TARGET SDL3_ttf::SDL3_ttf)
|
||||
find_package(SDL3_ttf REQUIRED)
|
||||
endif()
|
||||
# No version here: libakerror ships no akerrorConfigVersion.cmake, so asking
|
||||
# for one makes find_package reject every install. The floor is enforced by
|
||||
# the #error in include/akgl/error.h instead, which feature-tests
|
||||
# AKERR_FIRST_CONSUMER_STATUS.
|
||||
if(NOT TARGET akerror::akerror)
|
||||
find_package(akerror REQUIRED)
|
||||
endif()
|
||||
# 0.1 rather than bare: libakstdlib 0.1.0 ships an akstdlibConfigVersion.cmake
|
||||
# with SameMinorVersion compatibility, mirroring its soname, so this accepts
|
||||
# any 0.1.x and refuses 0.2 and 1.0. Unversioned, this path would silently
|
||||
# accept an ABI-incompatible libakstdlib.
|
||||
if(NOT TARGET akstdlib::akstdlib)
|
||||
find_package(akstdlib REQUIRED)
|
||||
find_package(akstdlib 0.1 REQUIRED)
|
||||
endif()
|
||||
if(NOT TARGET jansson::jansson)
|
||||
find_package(jansson)
|
||||
@@ -80,6 +89,8 @@ set(exec_prefix "\${prefix}")
|
||||
set(libdir "\${exec_prefix}/lib")
|
||||
set(includedir "\${prefix}/include")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/akgl.pc.in ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc @ONLY)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/akgl/version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h @ONLY)
|
||||
|
||||
# Tests use both relative paths and SDL_GetBasePath(), so stage fixtures beside
|
||||
# test executables in every out-of-tree build.
|
||||
@@ -102,6 +113,7 @@ add_library(akgl SHARED
|
||||
src/assets.c
|
||||
src/character.c
|
||||
src/draw.c
|
||||
src/error.c
|
||||
src/game.c
|
||||
src/controller.c
|
||||
src/heap.c
|
||||
@@ -113,21 +125,39 @@ add_library(akgl SHARED
|
||||
src/staticstring.c
|
||||
src/tilemap.c
|
||||
src/util.c
|
||||
src/version.c
|
||||
${GAMECONTROLLERDB_H}
|
||||
)
|
||||
|
||||
# While the major version is 0 the ABI is not stable across minor releases, so
|
||||
# the soname carries major.minor -- libakgl.so.0.1. A plain SOVERSION 0 would
|
||||
# claim 0.1.0 and 0.2.0 are interchangeable, which is exactly the silent
|
||||
# mispairing the soname is here to prevent. At 1.0.0 this becomes the major
|
||||
# alone, matching libakerror.
|
||||
if(PROJECT_VERSION_MAJOR EQUAL 0)
|
||||
set(AKGL_SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
else()
|
||||
set(AKGL_SOVERSION "${PROJECT_VERSION_MAJOR}")
|
||||
endif()
|
||||
|
||||
set_target_properties(akgl PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${AKGL_SOVERSION}
|
||||
)
|
||||
|
||||
add_library(akgl::akgl ALIAS akgl)
|
||||
|
||||
add_executable(charviewer util/charviewer.c)
|
||||
add_executable(test_semver_unit deps/semver/semver_unit.c)
|
||||
add_executable(akgl_test_semver_unit deps/semver/semver_unit.c)
|
||||
|
||||
# Every suite here is a standalone C program named tests/<name>.c, built as
|
||||
# test_<name> and registered with CTest under <name>.
|
||||
# akgl_test_<name> and registered with CTest under <name>.
|
||||
set(AKGL_TEST_SUITES
|
||||
actor
|
||||
bitmasks
|
||||
character
|
||||
controller
|
||||
error
|
||||
game
|
||||
heap
|
||||
json_helpers
|
||||
@@ -137,14 +167,19 @@ set(AKGL_TEST_SUITES
|
||||
staticstring
|
||||
tilemap
|
||||
util
|
||||
version
|
||||
)
|
||||
|
||||
# The executables carry an akgl_ prefix but the CTest names do not: a vendored
|
||||
# dependency is free to ship its own tests/version.c, and libakstdlib now does.
|
||||
# Its target is created by add_subdirectory even though EXCLUDE_FROM_ALL keeps
|
||||
# it from being built, so an unprefixed test_version here is a configure error.
|
||||
foreach(suite IN LISTS AKGL_TEST_SUITES)
|
||||
add_executable(test_${suite} tests/${suite}.c)
|
||||
add_test(NAME ${suite} COMMAND test_${suite})
|
||||
add_executable(akgl_test_${suite} tests/${suite}.c)
|
||||
add_test(NAME ${suite} COMMAND akgl_test_${suite})
|
||||
endforeach()
|
||||
|
||||
add_test(NAME semver_unit COMMAND test_semver_unit)
|
||||
add_test(NAME semver_unit COMMAND akgl_test_semver_unit)
|
||||
|
||||
set_tests_properties(
|
||||
${AKGL_TEST_SUITES} semver_unit
|
||||
@@ -155,6 +190,9 @@ set_tests_properties(
|
||||
target_include_directories(akgl PUBLIC
|
||||
include/
|
||||
deps/semver/
|
||||
# akgl/version.h is generated by configure_file, so it lives in the build tree
|
||||
# rather than beside the headers it is included from.
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/
|
||||
)
|
||||
|
||||
if(AKGL_COVERAGE)
|
||||
@@ -209,8 +247,8 @@ target_link_libraries(akgl
|
||||
)
|
||||
|
||||
foreach(suite IN LISTS AKGL_TEST_SUITES)
|
||||
target_link_libraries(test_${suite} PRIVATE akstdlib::akstdlib akerror::akerror akgl SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer jansson::jansson -lm)
|
||||
target_include_directories(test_${suite} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||
target_link_libraries(akgl_test_${suite} PRIVATE akstdlib::akstdlib akerror::akerror akgl SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer jansson::jansson -lm)
|
||||
target_include_directories(akgl_test_${suite} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/tests")
|
||||
endforeach()
|
||||
|
||||
target_link_libraries(charviewer PRIVATE akstdlib::akstdlib akerror::akerror akgl SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::SDL3_image SDL3_mixer::SDL3_mixer jansson::jansson -lm)
|
||||
@@ -230,7 +268,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
"$<TARGET_FILE_DIR:akstdlib::akstdlib>"
|
||||
)
|
||||
foreach(suite IN LISTS AKGL_TEST_SUITES)
|
||||
set_target_properties(test_${suite} PROPERTIES BUILD_RPATH "${AKGL_VENDORED_RPATH}")
|
||||
set_target_properties(akgl_test_${suite} PROPERTIES BUILD_RPATH "${AKGL_VENDORED_RPATH}")
|
||||
endforeach()
|
||||
set_target_properties(charviewer akgl PROPERTIES BUILD_RPATH "${AKGL_VENDORED_RPATH}")
|
||||
|
||||
@@ -285,8 +323,8 @@ if(Python3_FOUND)
|
||||
)
|
||||
endif()
|
||||
|
||||
set(main_lib_dest "lib/akgl-${MY_LIBRARY_VERSION}")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/akgl.pc DESTINATION "lib/pkgconfig/")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/akgl/version.h DESTINATION "include/akgl/")
|
||||
install(TARGETS akgl DESTINATION "lib/")
|
||||
install(FILES "deps/semver/semver.h" DESTINATION "include/")
|
||||
install(FILES "include/akgl/actor.h" DESTINATION "include/akgl/")
|
||||
|
||||
92
README.md
92
README.md
@@ -270,6 +270,98 @@ PASS(e, akgl_heap_release_string(width));
|
||||
```
|
||||
|
||||
|
||||
## Git hooks
|
||||
|
||||
The repository ships a `pre-commit` hook that keeps committed C sources in the
|
||||
project's canonical format (Emacs `cc-mode` "stroustrup"; see `AGENTS.md` for the
|
||||
full style guide). The hook lives in `scripts/hooks/` and is version controlled,
|
||||
but **Git configuration is not cloned**, so every clone has to be pointed at it
|
||||
once:
|
||||
|
||||
```sh
|
||||
git config core.hooksPath scripts/hooks
|
||||
```
|
||||
|
||||
Confirm it took effect:
|
||||
|
||||
```sh
|
||||
git rev-parse --git-path hooks # should print: scripts/hooks
|
||||
```
|
||||
|
||||
That is the whole installation. The hook is already committed with its
|
||||
executable bit set, so nothing needs `chmod`.
|
||||
|
||||
### What the hook does
|
||||
|
||||
On each commit it looks at the **staged** content of any added, copied, modified,
|
||||
or renamed `.c`/`.h` file under `src/`, `include/`, `tests/`, or `util/`, and
|
||||
reindents it if it does not already match the canonical style. Checking the
|
||||
staged content rather than the working tree means what lands in the commit is
|
||||
what was actually verified.
|
||||
|
||||
When a file needs reindenting, the hook fixes it in the working tree and
|
||||
re-stages it — but only when the index and working tree agree for that file. If
|
||||
they differ, you have staged part of a file with `git add -p`, and re-staging
|
||||
would sweep your unstaged work into the commit. Rather than do that silently the
|
||||
hook stops and prints the commands to run yourself:
|
||||
|
||||
```sh
|
||||
scripts/reindent.sh path/to/file.c
|
||||
git add path/to/file.c
|
||||
```
|
||||
|
||||
`include/akgl/SDL_GameControllerDB.h` is generated and always skipped.
|
||||
|
||||
### Requirements
|
||||
|
||||
The hook drives Emacs in batch mode, because `cc-mode`'s indentation engine is
|
||||
the definition of the style and `clang-format` cannot reproduce it exactly. If
|
||||
`emacs` is not on `PATH` the hook prints a warning and allows the commit — a
|
||||
hook that refuses to run without an optional tool only teaches people to reach
|
||||
for `--no-verify`. Install Emacs to get the check; without it, formatting is on
|
||||
you.
|
||||
|
||||
### Bypassing and uninstalling
|
||||
|
||||
Skip the hook for a single commit:
|
||||
|
||||
```sh
|
||||
git commit --no-verify
|
||||
```
|
||||
|
||||
Remove it entirely:
|
||||
|
||||
```sh
|
||||
git config --unset core.hooksPath
|
||||
```
|
||||
|
||||
### If you already have local hooks
|
||||
|
||||
`core.hooksPath` **replaces** the hooks directory outright — once it is set, Git
|
||||
stops reading `.git/hooks/` altogether, so any hooks you keep there will silently
|
||||
stop firing. If that matters, leave `core.hooksPath` unset and symlink just this
|
||||
one hook instead:
|
||||
|
||||
```sh
|
||||
ln -s ../../scripts/hooks/pre-commit .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
### Formatting without the hook
|
||||
|
||||
The hook is a convenience, not the source of truth. The same check is available
|
||||
directly, and is what you would run in CI:
|
||||
|
||||
```sh
|
||||
scripts/reindent.sh --check # list non-conforming files; exit 1 if any
|
||||
scripts/reindent.sh # reindent every tracked C source in place
|
||||
scripts/reindent.sh src/game.c # reindent specific files
|
||||
```
|
||||
|
||||
`--check` exits `0` when everything conforms, `1` when a file needs reindenting,
|
||||
and `2` if Emacs is missing or the script cannot run — so a CI job that treats
|
||||
any non-zero status as failure will not mistake a broken toolchain for a clean
|
||||
tree.
|
||||
|
||||
## Mutation testing
|
||||
|
||||
The mutation harness makes one deliberate source-code change at a time in a scratch copy, then rebuilds and runs the passing CTest suite to measure whether tests detect the change. The known-failing `character` test is excluded by default.
|
||||
|
||||
487
TODO.md
487
TODO.md
@@ -1,5 +1,369 @@
|
||||
# TODO
|
||||
|
||||
## Internal consistency
|
||||
|
||||
Findings from a sweep of `src/` and `include/`. These are consistency and
|
||||
convention problems, not new functional defects — where one has a functional
|
||||
consequence it is called out. Ordered roughly by blast radius. Items that
|
||||
overlap the existing **Defects** list are cross-referenced rather than repeated.
|
||||
|
||||
### 1. Public naming conventions
|
||||
|
||||
1. **Include guards use three different schemes.** `_AKGL_ACTOR_H_`,
|
||||
`_AKGL_CHARACTER_H_`, `_AKGL_GAME_H_`, `_AKGL_HEAP_H_`, `_AKGL_ITERATOR_H_`,
|
||||
`_AKGL_SPRITE_H_`, and `_AKGL_TYPES_H_` carry the project prefix; `_ASSETS_H_`,
|
||||
`_CONTROLLER_H_`, `_DRAW_H_`, `_ERROR_H_`, `_JSON_HELPERS_H_`, `_PHYSICS_H_`,
|
||||
`_REGISTRY_H_`, `_RENDERER_H_`, `_TEXT_H_`, `_TILEMAP_H_`, and `_UTIL_H_` do
|
||||
not. Pick `_AKGL_<FILE>_H_` everywhere.
|
||||
|
||||
The worst case is `include/akgl/staticstring.h:6`, which guards with
|
||||
`_STRING_H_` — a name several libc implementations use for their own
|
||||
`<string.h>`. The same file then does `#include "string.h"` (line 9) with
|
||||
quotes, which is a relative-first lookup that only reaches the system header
|
||||
by accident. Rename the guard to `_AKGL_STATICSTRING_H_` and use
|
||||
`#include <string.h>`.
|
||||
|
||||
2. **Function names contradict the stated convention.** `AGENTS.md` says public
|
||||
symbols use `akgl_` and types use `akgl_TypeName`. Exceptions:
|
||||
- `akgl_Actor_cmhf_left_on` and its seven siblings (`include/akgl/actor.h:196-252`)
|
||||
embed the *type* name in a *function* name, unlike every other actor entry
|
||||
point (`akgl_actor_*`). Rename to `akgl_actor_cmhf_*`.
|
||||
- `akgl_game_updateFPS` (`include/akgl/game.h:104`) is camelCase; every other
|
||||
function is snake_case.
|
||||
- `akgl_render_init2d` (`include/akgl/renderer.h:83`) puts the `2d` at the end
|
||||
while the six functions it installs are `akgl_render_2d_*`. Make it
|
||||
`akgl_render_2d_init`.
|
||||
- `akgl_sprite_sheet_coords_for_frame` (`include/akgl/sprite.h:86`) spells it
|
||||
`sprite_sheet`; `akgl_spritesheet_initialize` and `akgl_heap_next_spritesheet`
|
||||
spell it `spritesheet`.
|
||||
|
||||
3. **Two public types have no prefix at all.** `point` and `RectanglePoints`
|
||||
(`include/akgl/util.h:13-25`) are unprefixed, and `RectanglePoints` is
|
||||
PascalCase with no namespace. Both are dumped into every translation unit
|
||||
that includes `util.h`. Rename to `akgl_Point` / `akgl_RectanglePoints`.
|
||||
|
||||
4. **Global variables use four different conventions.** `window`, `bgm`, `game`,
|
||||
`gamemap`, `renderer`, `physics`, and `camera` (`src/game.c:26-43`) are
|
||||
unprefixed single common words exported from a shared library — `renderer`
|
||||
and `camera` in particular are very likely to collide with a consuming game.
|
||||
Alongside them the same file exports `akgl_mixer` and `akgl_tracks` (prefixed),
|
||||
`_akgl_renderer` / `_akgl_camera` / `_akgl_physics` / `_akgl_gamemap`
|
||||
(underscore-prefixed, which is reserved at file scope), and elsewhere
|
||||
`HEAP_ACTOR`…`HEAP_STRING` (`src/heap.c:17-21`) and `GAME_ControlMaps`
|
||||
(`src/controller.c:13`) are SCREAMING_SNAKE, which the convention reserves for
|
||||
constants and macros. Settle on `akgl_` for all exported objects.
|
||||
|
||||
5. **`AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH`** (`include/akgl/character.h:13`) is a
|
||||
character constant carrying the `SPRITE` prefix, and lives in `character.h`.
|
||||
Rename to `AKGL_CHARACTER_MAX_NAME_LENGTH`.
|
||||
|
||||
6. **`AKGL_TIME_ONESEC_MS` is misnamed and the error is live.**
|
||||
`include/akgl/game.h:22` defines it as `1000000`. One second in milliseconds
|
||||
is `1000`; `1000000` is the number of nanoseconds in a *millisecond*. The name
|
||||
says "one second" but the value means "one millisecond", so:
|
||||
- `src/character.c:209` and `src/sprite.c:141` use it correctly as a
|
||||
milliseconds-to-nanoseconds scale factor.
|
||||
- `src/game.c:136` uses it as documented — a one-second budget for the state
|
||||
lock — in a loop that advances `totaltime += 100` alongside `SDL_Delay(100)`.
|
||||
The loop therefore runs 10,000 iterations of 100 ms, so `akgl_game_state_lock`
|
||||
blocks for roughly 16 minutes rather than 1 second before reporting failure.
|
||||
|
||||
Rename to `AKGL_TIME_ONEMS_NS` to match `AKGL_TIME_ONESEC_NS`, and give
|
||||
`akgl_game_state_lock` a real one-second budget.
|
||||
|
||||
### 2. Header/implementation surface drift
|
||||
|
||||
7. **Nineteen non-static functions are defined in `src/` but declared in no
|
||||
header.** They have external linkage and public-looking names, so they are
|
||||
part of the ABI whether intended or not, and no consumer can call them:
|
||||
|
||||
`akgl_game_load_objectnamemap`, `akgl_game_load_versioncmp`,
|
||||
`akgl_game_save_actors`, `akgl_game_save_actorname_iterator`,
|
||||
`akgl_game_save_charactername_iterator`, `akgl_game_save_spritename_iterator`,
|
||||
`akgl_game_save_spritesheetname_iterator` (`src/game.c`);
|
||||
`akgl_get_json_properties_double`, `akgl_get_json_properties_float`,
|
||||
`akgl_get_json_properties_number`, `akgl_tilemap_load_layer_image`,
|
||||
`akgl_tilemap_load_layer_object_actor`, `akgl_tilemap_load_physics`
|
||||
(`src/tilemap.c`); `akgl_path_relative_from`, `akgl_path_relative_root`
|
||||
(`src/util.c`); `gamepad_handle_added`, `gamepad_handle_button_down`,
|
||||
`gamepad_handle_button_up`, `gamepad_handle_removed` (`src/controller.c`).
|
||||
|
||||
Each should be either declared in its header or made `static`. Note that
|
||||
`tilemap.h` already has a "part of the internal API, exposed here for unit
|
||||
testing" block — the tilemap entries belong there.
|
||||
|
||||
8. **`akgl_game_init_screen` is declared but never defined**
|
||||
(`include/akgl/game.h:100`). Same failure mode as **Defects → Known and still
|
||||
open #10**, which covers the four `akgl_controller_handle_*` declarations;
|
||||
fold this one into that item.
|
||||
|
||||
9. **Static helpers use three different naming styles.** `actor_visible`
|
||||
(`src/actor.c:185`) is bare; `akgl_character_load_json_inner` and
|
||||
`akgl_character_load_json_state_int_from_strings` (`src/character.c:101,132`)
|
||||
and `akgl_sprite_load_json_spritesheet` (`src/sprite.c:51`) carry the full
|
||||
public prefix; `gamepad_handle_*` (`src/controller.c:121+`) uses a third
|
||||
subsystem word that appears nowhere else. Adopt one rule — the clearest is
|
||||
that `static` helpers drop the `akgl_` prefix, since it exists to avoid
|
||||
external collisions.
|
||||
|
||||
10. **Parameter names disagree between declaration and definition.** Doxygen
|
||||
documents the header spelling, so the generated docs describe names the
|
||||
implementation does not use:
|
||||
|
||||
| Header | Implementation |
|
||||
|---|---|
|
||||
| `character.h:41` `basechar` | `character.c:21` `obj` |
|
||||
| `character.h:69` `props` | `character.c:75` `registry` |
|
||||
| `heap.h:121` `ptr` | `heap.c:143` `basechar` |
|
||||
| `registry.h:97` `value` | `registry.c:163` `src` |
|
||||
| `json_helpers.h:134` `e` | `json_helpers.c:149` `err` |
|
||||
| `tilemap.h:134,143` `dest` | `tilemap.c:646,767` `map` |
|
||||
|
||||
The `tilemap.h` pair is the most misleading: the parameter is the map being
|
||||
*read* and drawn, but it is named `dest` and documented as "Output
|
||||
destination populated by the function".
|
||||
|
||||
11. **Object-pool size macros are defined twice, and the override hook is
|
||||
dead.** `heap.h:15-29` wraps `AKGL_MAX_HEAP_ACTOR`, `_SPRITE`, `_SPRITESHEET`,
|
||||
`_CHARACTER`, and `_STRING` in `#ifndef` guards so a consumer can override
|
||||
them, but `actor.h:65`, `sprite.h:19-20`, and `character.h:14` define the same
|
||||
four unconditionally and are included from `heap.h:9-11`. Whichever header
|
||||
lands first wins and the `#ifndef` never fires, so the override mechanism
|
||||
cannot work. Define each pool size once — `heap.h` is the natural home.
|
||||
|
||||
12. **Headers rely on their includers for types.** `iterator.h` uses `uint32_t`
|
||||
without `<stdint.h>`; `json_helpers.h` uses `json_t` without `<jansson.h>`;
|
||||
`util.h` uses `SDL_FRect` and `bool` without any SDL include; `text.h` uses
|
||||
`SDL_Color` and `akerr_ErrorContext` without including SDL or `akerror.h`;
|
||||
`controller.h` uses `akgl_Actor` without including `actor.h`. Each compiles
|
||||
only because of `.c`-file include ordering. Headers should be self-contained.
|
||||
|
||||
13. **Include spelling is split between quoted and angled forms for the same
|
||||
directory.** `actor.h:10-11`, `character.h:10-11`, `controller.h:11`,
|
||||
`game.h:11-14`, `json_helpers.h:10`, and `staticstring.h:9` use
|
||||
`#include "sibling.h"`; `physics.h:11-13`, `registry.h:9-10`, `renderer.h:13`,
|
||||
`tilemap.h:10-12`, and `util.h:10` use `#include <akgl/sibling.h>`. The
|
||||
angled form is correct for an installed library.
|
||||
|
||||
14. **Empty parameter lists.** `akgl_heap_init()`, `akgl_heap_init_actor()`,
|
||||
`akgl_registry_init*()`, `akgl_game_init()`, `akgl_game_init_screen()`, and
|
||||
`akgl_game_updateFPS()` declare `()` rather than `(void)`, while
|
||||
`akgl_controller_list_keyboards(void)`, `akgl_controller_open_gamepads(void)`,
|
||||
`akgl_game_lowfps(void)`, and `akgl_game_state_lock(void)` use `(void)`.
|
||||
Before C23 the two are not equivalent — `()` suppresses argument checking.
|
||||
`akgl_heap_init_actor` is even declared `()` in `heap.h:57` and defined
|
||||
`(void)` in `heap.c:48`.
|
||||
|
||||
15. **`AKERR_NOIGNORE` is applied inconsistently at definition sites.** Headers
|
||||
use it uniformly (except `akgl_sprite_sheet_coords_for_frame`, `sprite.h:86`,
|
||||
which omits it). Definitions are split even within one file: `registry.c:55,120,163,172`
|
||||
repeat it, `registry.c:27,44,63,71,79,96,104,112` do not. Since the attribute
|
||||
is already on the declaration, drop it from all definitions.
|
||||
|
||||
### 3. Error-handling pattern
|
||||
|
||||
16. **`*_RETURN` macros are used inside `ATTEMPT` blocks, which skips `CLEANUP`.**
|
||||
The established pattern is `FAIL_*_BREAK` / `CATCH` inside `ATTEMPT` and
|
||||
`*_RETURN` outside it. Violations:
|
||||
- `src/tilemap.c:52` — `SUCCEED_RETURN` on the success path of
|
||||
`akgl_get_json_tilemap_property` returns directly from inside `ATTEMPT`,
|
||||
bypassing the `CLEANUP` at line 54 that releases `tmpstr` and `typestr`.
|
||||
Every successful property lookup leaks two heap strings, and the string
|
||||
pool is only 256 entries.
|
||||
- `src/tilemap.c:620` — `FAIL_RETURN` inside `ATTEMPT`.
|
||||
- `src/controller.c:286-289,306` — `FAIL_ZERO_RETURN` / `FAIL_NONZERO_RETURN`
|
||||
inside `ATTEMPT`; `src/controller.c:383` — `SUCCEED_RETURN` inside `ATTEMPT`,
|
||||
which also leaves `akgl_controller_default` with no return statement on the
|
||||
path that falls out of `FINISH`.
|
||||
- `src/game.c:457,462` — `FAIL_NONZERO_RETURN` inside `ATTEMPT`, skipping the
|
||||
`fclose` in `CLEANUP` at line 482.
|
||||
|
||||
17. **NULL-check discipline varies by function.** In `src/json_helpers.c` only
|
||||
`akgl_get_json_string_value` (line 71) and `akgl_get_json_array_index_string`
|
||||
(line 128) validate `key`/`dest`; the other eight accessors validate only the
|
||||
container and then dereference `dest` unconditionally. In `src/physics.c` the
|
||||
arcade backends check `actor` but `akgl_physics_null_gravity`,
|
||||
`_null_collide`, and `_null_move` (lines 15-34) check only `self`. In
|
||||
`src/renderer.c:65,74`, `akgl_render_2d_frame_start` and `_frame_end`
|
||||
dereference `self->sdl_renderer` with no check on `self`, while
|
||||
`akgl_render_2d_draw_texture` (line 82) checks `self` first.
|
||||
|
||||
18. **Error-context variable naming is split between `errctx` and `e`,
|
||||
sometimes within one file.** `src/util.c` uses `e` in the path helpers
|
||||
(lines 32-116) and `errctx` in the geometry helpers (lines 118-259);
|
||||
`src/heap.c` uses `errctx` everywhere except `akgl_heap_init_actor` (line 50).
|
||||
`src/actor.c`, `character.c`, `json_helpers.c`, `registry.c`, `sprite.c`, and
|
||||
`tilemap.c` favor `errctx`; `game.c`, `physics.c`, `renderer.c`, and
|
||||
`controller.c` favor `e`. Pick one.
|
||||
|
||||
### 4. Types and macros
|
||||
|
||||
19. **`float`/`double` are used raw where `types.h` defines aliases.** `types.h`
|
||||
exports `float32_t` and `float64_t`, and the actor and character structs use
|
||||
`float32_t` throughout — but `akgl_get_json_number_value` takes `float *`
|
||||
(`json_helpers.h:55`), `akgl_get_json_double_value` takes `double *` (line 66),
|
||||
`akgl_PhysicsBackend`'s six drag/gravity fields are `double`
|
||||
(`physics.h:22-27`), and `akgl_Tilemap`'s perspective fields are `float`
|
||||
(`tilemap.h:105-108`). Either use the aliases consistently or delete them.
|
||||
|
||||
20. **`AKGL_COLLIDE_RECTANGLES` (`include/akgl/util.h:27`) has unbalanced
|
||||
parentheses** — three opens, two closes — so any use is a syntax error. It has
|
||||
no callers and duplicates `akgl_collide_rectangles`. Delete it.
|
||||
|
||||
21. **Bitmask macros are unparenthesized.** `AKGL_BITMASK_HAS(x, y)` expands to
|
||||
`(x & y) == y` with no outer parens, so `!AKGL_BITMASK_HAS(a, b)` parses as
|
||||
`!(a & b) == b`. No in-tree caller negates it today (`AKGL_BITMASK_HASNOT`
|
||||
exists for that), so this is latent rather than live. `AKGL_BITMASK_CLEAR(x)`
|
||||
(`game.h:86`) also carries a trailing semicolon inside the macro body, so
|
||||
normal use produces an empty statement. Parenthesize all five and drop the
|
||||
semicolon.
|
||||
|
||||
22. **The state and iterator bit macros mix forms.** `AKGL_ITERATOR_OP_UPDATE`
|
||||
(`iterator.h:15`) is written `1` while its 31 siblings are `1 << n`, and none
|
||||
of the `1 << n` values in `iterator.h` or `actor.h:15-49` are parenthesized.
|
||||
The hand-maintained trailing bit-pattern comments in `actor.h:34-49` are also
|
||||
wrong for the high word — they restart at `0000 0000 0000 0001` for bit 16
|
||||
rather than showing the full 32-bit value.
|
||||
|
||||
23. **`akgl_Frame` (`game.h:27-31`) is defined and never used** anywhere in
|
||||
`src/`, `include/`, `tests/`, or `util/`.
|
||||
|
||||
### 5. `AKGL_ACTOR_STATE_STRING_NAMES` disagrees with `actor.h`
|
||||
|
||||
24. **The array bound differs between declaration and definition.**
|
||||
`include/akgl/actor.h:57` declares `[AKGL_ACTOR_MAX_STATES+1]` (33);
|
||||
`src/actor_state_string_names.c:6` defines `[32]`. Any consumer that trusts
|
||||
the declared bound and reads index 32 reads past the object.
|
||||
|
||||
25. **Two entries name the wrong bit.** `actor.h` assigns bit 11 to
|
||||
`AKGL_ACTOR_STATE_MOVING_IN` and bit 12 to `AKGL_ACTOR_STATE_MOVING_OUT`, but
|
||||
`actor_state_string_names.c:18-19` puts `"AKGL_ACTOR_STATE_UNDEFINED_11"` and
|
||||
`"AKGL_ACTOR_STATE_UNDEFINED_12"` at those indices — names for macros that do
|
||||
not exist. Since `akgl_registry_init_actor_state_strings`
|
||||
(`src/registry.c:79-94`) builds `AKGL_REGISTRY_ACTOR_STATE_STRINGS` from this
|
||||
array and `akgl_character_load_json_state_int_from_strings`
|
||||
(`src/character.c:101`) resolves character-JSON state names through it, a
|
||||
character JSON can never bind a sprite to `MOVING_IN` or `MOVING_OUT`.
|
||||
|
||||
26. **The generation comment is stale.** `actor.h:53-55` says the file "is built
|
||||
by a utility script and not kept in git, see the Makefile for
|
||||
lib_src/actor_state_string_names.c". There is no Makefile (the project is
|
||||
CMake), no `lib_src/`, no such script under `scripts/` or `util/`, and the
|
||||
file *is* tracked in git at `src/actor_state_string_names.c`. Either restore
|
||||
the generator — which would fix items 24 and 25 by construction — or delete
|
||||
the comment and maintain the file by hand.
|
||||
|
||||
### 6. Doxygen drift
|
||||
|
||||
27. **Three struct doc comments in `tilemap.h` are rotated by one.**
|
||||
`akgl_TilemapObject` (line 31) is documented as "Stores tileset metadata,
|
||||
texture, and frame offsets" (that is `akgl_Tileset`); `akgl_TilemapLayer`
|
||||
(line 46) as "Represents an object embedded in a tilemap layer" (that is
|
||||
`akgl_TilemapObject`); `akgl_Tileset` (line 61) as "Stores tile, image, or
|
||||
object data for one map layer" (that is `akgl_TilemapLayer`).
|
||||
|
||||
28. **`point` is documented as "Represents a two-dimensional point"**
|
||||
(`util.h:12`) but has `x`, `y`, and `z`.
|
||||
|
||||
29. **Doc comments live on the definition for public functions.** The convention
|
||||
is header-side documentation, but `akgl_path_relative_root` (`util.c:23`),
|
||||
`akgl_path_relative_from` (`util.c:97`), the four `gamepad_handle_*`
|
||||
(`controller.c:114+`), the `akgl_game_save_*` iterators and
|
||||
`akgl_game_load_*` helpers (`game.c:173+`), and the tilemap helpers
|
||||
(`tilemap.c:90+`) are documented only in the `.c`. This is the same set as
|
||||
item 7 — resolving that resolves this.
|
||||
|
||||
### 7. Formatting
|
||||
|
||||
30. **A minority of files use a 2-column offset instead of the canonical
|
||||
4-column one.** The mix of tabs and spaces across most of `src/` is *not*
|
||||
disorder: it is exactly what Emacs `cc-mode` emits for the `stroustrup` style
|
||||
with `indent-tabs-mode t` and `tab-width 8` — depth 1 is four spaces, depth 2
|
||||
is one tab, depth 3 is a tab plus four spaces, and so on. Twelve of the
|
||||
seventeen `.c` files follow that ladder cleanly.
|
||||
|
||||
The genuine outliers indent at 2 columns: `src/json_helpers.c` (82 lines,
|
||||
except `akgl_get_json_with_default` at line 149 which is 4), `src/util.c`
|
||||
(37 lines, from `akgl_rectangle_points` at line 118 onward), `src/assets.c`
|
||||
(9), `src/staticstring.c` (7), `src/actor.c` (8, in `akgl_actor_add_child`
|
||||
at line 272), plus `include/akgl/util.h` (7) and
|
||||
`include/akgl/staticstring.h` (2).
|
||||
|
||||
**Resolved.** `AGENTS.md` now specifies the canonical style and the exact
|
||||
`cc-mode` settings; `.dir-locals.el` applies them in Emacs; and
|
||||
`scripts/reindent.sh` applies them in batch. The whole tree (32 files across
|
||||
`src/`, `include/`, `tests/`, and `util/`) has been reindented and is a fixed
|
||||
point of `scripts/reindent.sh --check`. Verified whitespace-only: apart from
|
||||
three trailing blank lines removed at EOF in `src/heap.c`, `src/registry.c`,
|
||||
and `tests/tilemap.c`, `git diff -w` over the reindent is empty, and the test
|
||||
results are unchanged (13/14, `character` still the intentional failure).
|
||||
`scripts/hooks/pre-commit` keeps it that way — enable with
|
||||
`git config core.hooksPath scripts/hooks`.
|
||||
|
||||
31. **Leftover debug code ships in the library.** `src/controller.c:91-97` logs
|
||||
four lines whenever `event->type == 768 && event->key.which == 11 &&
|
||||
event->key.key == 13` — hardcoded decimal values for a specific keyboard ID
|
||||
on a specific developer's machine, inside the per-event inner loop.
|
||||
|
||||
32. **Large commented-out blocks.** `src/sprite.c:115-120,157,185-198,210`;
|
||||
`src/character.c:192-199,215`; `src/assets.c:18-27,50`;
|
||||
`src/tilemap.c:583,596-598,640`. All are the same abandoned
|
||||
`SDL_GetBasePath()` path-prefixing approach, superseded by
|
||||
`akgl_path_relative`. Delete them.
|
||||
|
||||
33. **Unused locals.** `screenwidth`/`screenheight` (`game.c:53-54`), `curTime`
|
||||
(`game.c:499`), `curTime` and `j` (`renderer.c:114,116` — `j` is also shadowed
|
||||
by the inner loop at line 128), `target` (`character.c:59`), `result`
|
||||
(`util.c:37,73`), `opflags` (`heap.c:146`, declared and cleared but never
|
||||
read).
|
||||
|
||||
34. **`akgl_game_update`'s default flags OR the same bit twice.**
|
||||
`src/game.c:496` reads
|
||||
`(AKGL_ITERATOR_OP_LAYERMASK | AKGL_ITERATOR_OP_LAYERMASK)`. Given the loop
|
||||
that follows walks layers and calls `updatefunc`, the intent was almost
|
||||
certainly `AKGL_ITERATOR_OP_UPDATE | AKGL_ITERATOR_OP_LAYERMASK`.
|
||||
|
||||
35. **`akgl_draw_background` is the only public function outside the error
|
||||
protocol.** `include/akgl/draw.h:14` returns `void` and reports nothing;
|
||||
every other public entry point returns `akerr_ErrorContext *`. It also calls
|
||||
`SDL_SetRenderDrawColor` and `SDL_RenderFillRect` without checking `renderer`
|
||||
or `renderer->sdl_renderer`.
|
||||
|
||||
36. **`akgl_registry_init_actor` is the only registry initializer that destroys
|
||||
an existing registry** before recreating it (`src/registry.c:47-49`). The
|
||||
other seven leak the old `SDL_PropertiesID` on a second call. Either all of
|
||||
them should do it or none should.
|
||||
|
||||
37. **Redundant casts obscure the code.** `(json_t *)json` where `json` is
|
||||
already `json_t *`, `(akgl_Tilemap *)dest`, `(akgl_Actor *)actorobj`,
|
||||
`(char *)&obj->name` on an array that already decays — roughly 180 pointer
|
||||
casts across `src/`, a large majority of them no-ops, densest in
|
||||
`src/tilemap.c:150-624` and `src/character.c:144-172`.
|
||||
They suppress exactly the conversion warnings that would catch a real
|
||||
mismatch.
|
||||
|
||||
38. **`struct`-qualified parameters in two definitions.**
|
||||
`akgl_physics_null_move` (`src/physics.c:29`) and `akgl_physics_arcade_move`
|
||||
(`src/physics.c:80`) are defined with `struct akgl_PhysicsBackend *self`
|
||||
while the header and the other eight physics functions use the typedef.
|
||||
|
||||
39. **`text.c:19` validates the wrong argument.**
|
||||
`FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null filepath")` checks
|
||||
`name` a second time; `filepath` is never checked and is passed straight to
|
||||
`TTF_OpenFont`. Copy-paste of line 18.
|
||||
|
||||
40. **`akgl_path_relative` and `akgl_path_relative_from` disagree on the output
|
||||
parameter.** `akgl_path_relative` and `akgl_path_relative_root` take
|
||||
`akgl_String *dst`; `akgl_path_relative_from` takes `akgl_String **dst`
|
||||
(`src/util.c:105`). The `**` form matches the rest of the library
|
||||
(`akgl_get_json_string_value`, `akgl_get_property`, `akgl_heap_next_string`),
|
||||
which allocate when `*dest` is NULL. Related: **Defects → Known and still
|
||||
open #4**, which covers the fact that `akgl_path_relative_from` never writes
|
||||
`*dst` at all.
|
||||
|
||||
41. **`dst` vs `dest` for output parameters.** `akgl_string_copy` and the
|
||||
`akgl_path_relative*` family use `dst`; everything else uses `dest`.
|
||||
|
||||
## Coverage status
|
||||
|
||||
Generated with:
|
||||
@@ -195,6 +559,62 @@ Each was found by a test written to assert correct behavior.
|
||||
11. **`akgl_controller_pushmap` and `akgl_controller_default` accept negative map
|
||||
ids.** Both check `controlmapid >= AKGL_MAX_CONTROL_MAPS` but not
|
||||
`controlmapid < 0`, so a negative id indexes before `GAME_ControlMaps`.
|
||||
12. **A failed controller-DB fetch silently destroys the tracked fallback.**
|
||||
`include/akgl/SDL_GameControllerDB.h` is committed deliberately so the
|
||||
library still builds if upstream disappears. But `mkcontrollermappings.sh`
|
||||
has no `set -e` and never checks `curl`'s exit status: on a failed fetch it
|
||||
writes `mappings.txt` empty, then overwrites the good tracked header with
|
||||
`AKGL_SDL_GAMECONTROLLER_DB_LEN 0` and an empty initializer — and exits 0.
|
||||
Verified by pointing the script at an unresolvable host.
|
||||
|
||||
Two compounding problems:
|
||||
|
||||
- `add_custom_command` at `CMakeLists.txt:89-93` declares
|
||||
`OUTPUT include/akgl/SDL_GameControllerDB.h` as a *relative* path, which
|
||||
CMake resolves against the binary directory, while the script writes to
|
||||
the source directory. The declared output never appears, so the command is
|
||||
permanently out of date and re-runs on every build — making every build
|
||||
depend on network access and leaving the file dirty in the working tree
|
||||
each time.
|
||||
- `const char *SDL_GAMECONTROLLER_DB[] = {\n};` is an empty initializer,
|
||||
which is a constraint violation in ISO C and compiles only as a GCC
|
||||
extension.
|
||||
|
||||
Fix: have the script fetch to a temporary file, check `curl`'s status and a
|
||||
plausible minimum line count, and leave the existing header untouched on
|
||||
failure (exiting non-zero). Separately, make regeneration explicit — a
|
||||
dedicated `controllerdb` target, or an `OUTPUT` that matches where the
|
||||
script actually writes — so an ordinary build neither needs the network nor
|
||||
dirties the tree.
|
||||
13. **A stale build tree in the source directory breaks the coverage run.**
|
||||
`CMakeLists.txt:208` and `CMakeLists.txt:223` pass
|
||||
`--root "${CMAKE_CURRENT_SOURCE_DIR}"` to gcovr, and gcovr searches for
|
||||
`.gcda`/`.gcno` files under the root. The `--object-directory` argument on
|
||||
the line below each does *not* narrow that search: per `gcovr --help` it
|
||||
only identifies "the path between gcda files and the directory where the
|
||||
compiler was originally run". So every instrumented build tree left inside
|
||||
the source directory is folded into the report alongside the one actually
|
||||
being measured.
|
||||
|
||||
With a `build-coverage/` from an earlier session still present, a freshly
|
||||
configured `-DAKGL_COVERAGE=ON` tree fails in `coverage_reset`, before any
|
||||
test runs:
|
||||
|
||||
AssertionError: Got function akgl_game_lowfps on multiple lines: 45, 46.
|
||||
|
||||
45 and 46 are that function's line numbers before and after an unrelated
|
||||
`#include` was added to `src/game.c`: gcovr found 18 stale `.gcno` files
|
||||
describing the old layout, merged them with the current ones, and could not
|
||||
reconcile the two. Moving `build-coverage/` aside makes the same tree pass
|
||||
18/18. Verified with gcovr 7.0.
|
||||
|
||||
The `build*/` entry in `.gitignore` hides these trees from `git status`,
|
||||
which makes the state easier to get into and no easier to notice.
|
||||
|
||||
Fix: pass the build tree to gcovr as an explicit search path instead of
|
||||
letting it default to `--root`, so only the tree under measurement is
|
||||
considered. Touches the two `add_test` blocks at `CMakeLists.txt:204-211`
|
||||
and `CMakeLists.txt:220-229`; nothing outside the `AKGL_COVERAGE` branch.
|
||||
|
||||
## Build notes
|
||||
|
||||
@@ -206,6 +626,73 @@ not saw every test abort before `main()` with "cannot open shared object file".
|
||||
`CMakeLists.txt` now sets `BUILD_RPATH` on the library, the utility, and every
|
||||
test target, and prepends the build tree to `LD_LIBRARY_PATH` for the CTest run.
|
||||
|
||||
## API gaps blocking akbasic
|
||||
|
||||
`akbasic` (the C port of the BASIC interpreter, `source.starfort.tech/andrew/akbasic`) is
|
||||
being built to link into `libakgl` as a scripting engine for game authors. Its interpreter
|
||||
core is complete and passes its whole acceptance corpus against a stdio text sink; the
|
||||
`libakgl`-backed sink and the graphics, sound and console verbs of Commodore BASIC 7.0 are
|
||||
blocked on the four gaps below.
|
||||
|
||||
These are filed here rather than worked around in `akbasic` because growing `libakgl` to serve
|
||||
a consumer is the wanted outcome. Each entry says what the BASIC verb needs, what the
|
||||
`akgl_*` entry point should look like, and what would cover it.
|
||||
|
||||
1. **No way to measure rendered text.** `include/akgl/text.h` exposes `akgl_text_loadfont()`
|
||||
and `akgl_text_rendertextat()`, and nothing that reports how large a string will be in a
|
||||
given font. A terminal-style text surface cannot be built on that: a cursor needs the
|
||||
advance width of one cell, and wrapping needs to know where a string crosses the right
|
||||
margin. The reference interpreter got this from SDL2_ttf's `font.SizeUTF8("A")` and derived
|
||||
its whole character grid from it.
|
||||
|
||||
Wants `akerr_ErrorContext AKERR_NOIGNORE *akgl_text_measure(TTF_Font *font, char *text, int *w, int *h);`
|
||||
over `TTF_GetStringSize`, and probably a companion
|
||||
`akgl_text_measure_wrapped(font, text, wraplength, w, h)` matching the `wraplength`
|
||||
argument `akgl_text_rendertextat` already takes. Tests: a known string in a known font at a
|
||||
known size, the empty string, and a wrapped string wide enough to force two lines.
|
||||
|
||||
This is the only one of the four that blocks work already designed and waiting. **`akbasic`
|
||||
cannot render any output through `libakgl` until it lands.**
|
||||
|
||||
2. **No immediate-mode drawing.** `include/akgl/draw.h` declares exactly one function,
|
||||
`akgl_draw_background(int w, int h)`, and `src/draw.c` is at 0% coverage. BASIC 7.0's
|
||||
graphics verbs are all immediate-mode plotting against the current screen: `DRAW` (line and
|
||||
point), `BOX`, `CIRCLE`, `PAINT` (flood fill), `LOCATE` (set the pixel cursor), `COLOR`,
|
||||
and `SSHAPE`/`GSHAPE` (save and restore a rectangle of pixels).
|
||||
|
||||
Wants an `akgl_draw_*` family taking the renderer the host already initialized --
|
||||
`akgl_draw_line`, `_rect`, `_filled_rect`, `_circle`, `_point`, `_flood_fill`,
|
||||
`_copy_region` -- in the shape of the existing `akgl_render_2d_draw_texture`. SDL3's
|
||||
`SDL_RenderLine`/`SDL_RenderRect`/`SDL_RenderFillRect` cover most of it; the circle and the
|
||||
flood fill do not exist in SDL3 and need writing. Tests belong with the offscreen renderer
|
||||
harness described under "Remaining work": render a known shape, read the target back, and
|
||||
compare against a reference surface with the existing `akgl_compare_sdl_surfaces`.
|
||||
|
||||
3. **No audio API at all.** `SDL3_mixer` is a vendored dependency and `registry.h` declares
|
||||
`AKGL_REGISTRY_MUSIC`, but there is no `src/audio.c`, no `include/akgl/audio.h`, and no
|
||||
`akgl_*` symbol that opens a mixer, loads a chunk, or plays a note. BASIC 7.0's sound verbs
|
||||
are `SOUND` (a tone on a voice, with a duration), `PLAY` (a string of notes in a
|
||||
Commodore-specific notation), `ENVELOPE` (ADSR per voice), `FILTER`, `VOL` and `TEMPO`.
|
||||
|
||||
`PLAY` and `ENVELOPE` want a synthesised voice rather than a sample, which SDL3_mixer does
|
||||
not provide directly -- the honest first step is a small tone generator feeding
|
||||
`SDL_AudioStream`, with `akgl_audio_init`, `akgl_audio_tone(voice, hz, ms)`,
|
||||
`akgl_audio_envelope(voice, a, d, s, r)` and `akgl_audio_volume(level)`. This is the largest
|
||||
of the four and the one most worth designing before writing. Tests can run under the dummy
|
||||
audio driver and assert state transitions rather than sound.
|
||||
|
||||
4. **No non-blocking keystroke read.** `include/akgl/controller.h` is built around SDL event
|
||||
handlers the host pumps (`akgl_controller_handle_event` and friends), which suits a game
|
||||
loop and does not suit `GET` and `GETKEY` -- those ask "is there a keystroke waiting, yes or
|
||||
no" and must not require the interpreter to own the event loop. Goal 3 of `akbasic` forbids
|
||||
it owning one.
|
||||
|
||||
Wants `akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_poll_key(int *keycode, bool *available);`
|
||||
reading a small ring buffer that `akgl_controller_handle_event` already fills, so the host
|
||||
keeps pumping events and the interpreter drains characters at its own pace. Tests: push
|
||||
synthetic `SDL_EVENT_KEY_DOWN` events through the existing handler and drain them, plus the
|
||||
empty-buffer and overflow cases.
|
||||
|
||||
## Carried over
|
||||
|
||||
1. **Make character-to-sprite state bindings release their references symmetrically.**
|
||||
|
||||
2
deps/libakerror
vendored
2
deps/libakerror
vendored
Submodule deps/libakerror updated: 0c0d81249f...5ff87908e7
2
deps/libakstdlib
vendored
2
deps/libakstdlib
vendored
Submodule deps/libakstdlib updated: a87cbfb26d...95e5002512
@@ -6,21 +6,73 @@
|
||||
#ifndef _ERROR_H_
|
||||
#define _ERROR_H_
|
||||
|
||||
#include <akerror.h>
|
||||
|
||||
/*
|
||||
* libakerror 1.0.0 is the floor. That release moved the status-name table into a
|
||||
* private registry -- AKERR_MAX_ERR_VALUE and __AKERR_ERROR_NAMES are gone, the
|
||||
* registry entry points raise akerr_ErrorContext * instead of returning int, and
|
||||
* the library gained an soname -- so a translation unit that pairs this header
|
||||
* with a pre-1.0.0 akerror.h is an ABI mismatch, not just a compile problem.
|
||||
*
|
||||
* libakerror publishes no version macro, so this feature-tests on
|
||||
* AKERR_FIRST_CONSUMER_STATUS, which that release introduced, rather than on a
|
||||
* version number that does not exist. Without the guard an embedded build is
|
||||
* fine but a stale installed header fails much further in, on the AKGL_ERR_*
|
||||
* codes below and again inside src/heap.c.
|
||||
*
|
||||
* See deps/libakerror/UPGRADING.md.
|
||||
*/
|
||||
#ifndef AKERR_FIRST_CONSUMER_STATUS
|
||||
#error "libakgl requires libakerror >= 1.0.0: the akerror.h on the include path predates the status registry. Rebuild and reinstall libakerror."
|
||||
#endif
|
||||
|
||||
// This macro is used to silence warnings on string concatenation operations that may fail.
|
||||
// e.g., combining two element of PATH_MAX into a string buffer of AKGL_STRING_MAX_LENGTH.
|
||||
// We have to draw a line in the sand somewhere or we will just let our buffers grow forever
|
||||
// to keep the compiler happy.
|
||||
#define DISABLE_GCC_WARNING_FORMAT_TRUNCATION \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
#define DISABLE_GCC_WARNING_FORMAT_TRUNCATION \
|
||||
_Pragma("GCC diagnostic push") \
|
||||
_Pragma("GCC diagnostic ignored \"-Wformat-truncation\"")
|
||||
|
||||
#define RESTORE_GCC_WARNINGS \
|
||||
#define RESTORE_GCC_WARNINGS \
|
||||
_Pragma("GCC diagnostic pop")
|
||||
|
||||
#define AKGL_ERR_SDL (AKERR_LAST_ERRNO_VALUE + 18)
|
||||
#define AKGL_ERR_REGISTRY (AKERR_LAST_ERRNO_VALUE + 19)
|
||||
#define AKGL_ERR_HEAP (AKERR_LAST_ERRNO_VALUE + 20)
|
||||
#define AKGL_ERR_BEHAVIOR (AKERR_LAST_ERRNO_VALUE + 21)
|
||||
#define AKGL_ERR_LOGICINTERRUPT (AKERR_LAST_ERRNO_VALUE + 22)
|
||||
// libakerror reserves statuses 0-255 for the host's errno values and its own
|
||||
// AKERR_* codes; consumers allocate from AKERR_FIRST_CONSUMER_STATUS upward.
|
||||
// These are fixed offsets from that base rather than from AKERR_LAST_ERRNO_VALUE
|
||||
// so that a libc which grows an errno cannot move them out from under us.
|
||||
//
|
||||
// akgl_error_init() reserves this whole band in one call and registers a name
|
||||
// for every code below. Add a code here and you must name it there, or it
|
||||
// prints as "Unknown Error" in every stack trace that carries it.
|
||||
#define AKGL_ERR_OWNER "libakgl"
|
||||
#define AKGL_ERR_BASE AKERR_FIRST_CONSUMER_STATUS
|
||||
|
||||
#define AKGL_ERR_SDL (AKGL_ERR_BASE + 0) /** An SDL call failed; the message carries SDL_GetError() */
|
||||
#define AKGL_ERR_REGISTRY (AKGL_ERR_BASE + 1) /** A registry property or lookup operation failed */
|
||||
#define AKGL_ERR_HEAP (AKGL_ERR_BASE + 2) /** A heap pool has no free object left to hand out */
|
||||
#define AKGL_ERR_BEHAVIOR (AKGL_ERR_BASE + 3) /** A component did not behave the way its contract requires */
|
||||
#define AKGL_ERR_LOGICINTERRUPT (AKGL_ERR_BASE + 4) /** Actor logic is telling the physics simulator to skip it */
|
||||
|
||||
// One past the last libakgl status. The reservation is all-or-nothing -- a
|
||||
// subset or superset of an existing one is refused -- so this must stay one
|
||||
// past the highest code above.
|
||||
#define AKGL_ERR_LIMIT (AKGL_ERR_BASE + 5)
|
||||
#define AKGL_ERR_COUNT (AKGL_ERR_LIMIT - AKGL_ERR_BASE)
|
||||
|
||||
/**
|
||||
* @brief Claim the libakgl status band and register a name for every code in it.
|
||||
*
|
||||
* Call this before anything else in libakgl. Every other subsystem raises
|
||||
* AKGL_ERR_* codes, and a code raised before this runs carries no name into its
|
||||
* stack trace. Repeating the call is a no-op, so a program that cannot order its
|
||||
* initialization precisely may call it more than once.
|
||||
*
|
||||
* @throws AKERR_STATUS_RANGE_OVERLAP When another component already owns part of the band.
|
||||
* @throws AKERR_STATUS_RANGE_FULL When libakerror has no reservation slots left.
|
||||
* @throws AKERR_STATUS_NAME_FULL When libakerror's name registry is full.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_error_init(void);
|
||||
|
||||
#endif // _ERROR_H_
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
#include "tilemap.h"
|
||||
#include "renderer.h"
|
||||
#include "physics.h"
|
||||
|
||||
#define AKGL_VERSION "0.1.0"
|
||||
// AKGL_VERSION used to be defined here by hand, which is how akgl.pc came to
|
||||
// ship an empty Version field: nothing tied the two together.
|
||||
#include <akgl/version.h>
|
||||
|
||||
#define AKGL_GAME_AUDIO_TRACK_BGM 1
|
||||
#define AKGL_GAME_AUDIO_MAX_TRACKS 64
|
||||
@@ -140,5 +141,5 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_state_unlock(void);
|
||||
* @throws AKERR_* Propagates an error reported by a delegated operation.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags);
|
||||
|
||||
|
||||
#endif //_AKGL_GAME_H_
|
||||
|
||||
@@ -26,7 +26,7 @@ typedef struct akgl_PhysicsBackend {
|
||||
double gravity_y;
|
||||
double gravity_z;
|
||||
SDL_Time gravity_time;
|
||||
SDL_Time timer_gravity;
|
||||
SDL_Time timer_gravity;
|
||||
} akgl_PhysicsBackend;
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
/** @brief Provides a fixed-capacity, heap-managed string buffer. */
|
||||
typedef struct
|
||||
{
|
||||
int refcount;
|
||||
char data[AKGL_MAX_STRING_LENGTH];
|
||||
int refcount;
|
||||
char data[AKGL_MAX_STRING_LENGTH];
|
||||
} akgl_String;
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,17 +11,17 @@
|
||||
|
||||
/** @brief Represents a two-dimensional point. */
|
||||
typedef struct point {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} point;
|
||||
|
||||
/** @brief Stores the corners of an axis-aligned rectangle. */
|
||||
typedef struct RectanglePoints {
|
||||
point topleft;
|
||||
point topright;
|
||||
point bottomleft;
|
||||
point bottomright;
|
||||
point topleft;
|
||||
point topright;
|
||||
point bottomleft;
|
||||
point bottomright;
|
||||
} RectanglePoints;
|
||||
|
||||
#define AKGL_COLLIDE_RECTANGLES(r1x, r1y, r1w, r1h, r2x, r2y, r2w, r2h) ((r1x < (r2x + r2w)) || ((r1x + r1w) > r2x)
|
||||
|
||||
56
include/akgl/version.h.in
Normal file
56
include/akgl/version.h.in
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @file version.h
|
||||
* @brief Declares the libakgl version, both as compiled against and as linked.
|
||||
*
|
||||
* GENERATED FILE -- edit include/akgl/version.h.in, never the copy in the build
|
||||
* tree. Every value here comes from the project() call in CMakeLists.txt, which
|
||||
* is also what sets the shared library's VERSION and SOVERSION and the Version
|
||||
* field in akgl.pc. One number, one place, so the header, the soname and
|
||||
* pkg-config cannot drift apart.
|
||||
*
|
||||
* AKGL_VERSION is the version you *compiled against*. akgl_version() reports the
|
||||
* version of the libakgl you actually *linked*. They disagree when a stale
|
||||
* shared library is ahead of the new one on the loader path -- the failure the
|
||||
* soname exists to prevent and this pair exists to diagnose.
|
||||
*/
|
||||
|
||||
#ifndef _AKGL_VERSION_H_
|
||||
#define _AKGL_VERSION_H_
|
||||
|
||||
#define AKGL_VERSION "@PROJECT_VERSION@"
|
||||
#define AKGL_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
||||
#define AKGL_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
||||
#define AKGL_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
||||
|
||||
/**
|
||||
* @brief True when the headers on the include path are at least the given version.
|
||||
*
|
||||
* For consumers that must build against more than one libakgl release. This is
|
||||
* the test libakstdlib could not write against libakerror, which published no
|
||||
* version macro and had to feature-test on AKERR_FIRST_CONSUMER_STATUS instead.
|
||||
*
|
||||
* @code
|
||||
* #if AKGL_VERSION_AT_LEAST(0, 2, 0)
|
||||
* akgl_something_new();
|
||||
* #endif
|
||||
* @endcode
|
||||
*/
|
||||
#define AKGL_VERSION_AT_LEAST(major, minor, patch) \
|
||||
((AKGL_VERSION_MAJOR > (major)) || \
|
||||
(AKGL_VERSION_MAJOR == (major) && AKGL_VERSION_MINOR > (minor)) || \
|
||||
(AKGL_VERSION_MAJOR == (major) && AKGL_VERSION_MINOR == (minor) && \
|
||||
AKGL_VERSION_PATCH >= (patch)))
|
||||
|
||||
/**
|
||||
* @brief Report the version of the libakgl that is actually linked.
|
||||
*
|
||||
* Returns "major.minor.patch". Compare it against AKGL_VERSION to detect a
|
||||
* stale shared library. Never returns NULL, and the storage is static -- the
|
||||
* caller must not free or modify it.
|
||||
*
|
||||
* This returns a string rather than an akerr_ErrorContext * because it cannot
|
||||
* fail, the same reason akerr_name_for_status() returns a name directly.
|
||||
*/
|
||||
const char *akgl_version(void);
|
||||
|
||||
#endif // _AKGL_VERSION_H_
|
||||
98
scripts/hooks/pre-commit
Executable file
98
scripts/hooks/pre-commit
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Reindent staged C sources to the canonical style before the commit is made.
|
||||
# See AGENTS.md -> "Coding Style".
|
||||
#
|
||||
# Enable with:
|
||||
# git config core.hooksPath scripts/hooks
|
||||
# Bypass a single commit with `git commit --no-verify`.
|
||||
#
|
||||
# The hook checks the *staged* content, not the working tree, so what gets
|
||||
# committed is what was verified. When a file needs reindenting it is fixed in
|
||||
# the working tree and re-staged -- but only when the working tree and the index
|
||||
# agree for that file. If they disagree (a partial `git add -p`), re-staging
|
||||
# would sweep unstaged work into the commit, so the hook stops and asks you to
|
||||
# do it yourself.
|
||||
|
||||
set -eu
|
||||
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
reindent="$root/scripts/reindent.sh"
|
||||
|
||||
# Leave conflict resolution alone.
|
||||
if [ -e "$root/.git/MERGE_HEAD" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -x "$reindent" ]; then
|
||||
echo "pre-commit: $reindent missing or not executable; skipping style check" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Without Emacs the canonical style cannot be applied or even checked. Warn
|
||||
# rather than block: a hook that fails closed on a missing optional tool just
|
||||
# teaches everyone to pass --no-verify.
|
||||
if ! command -v emacs >/dev/null 2>&1; then
|
||||
echo "pre-commit: emacs not found; skipping reindent (see AGENTS.md)" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
staged=$(git diff --cached --name-only --diff-filter=ACMR \
|
||||
| grep -E '^(src|include|tests|util)/.*\.[ch]$' \
|
||||
| grep -v -x -F 'include/akgl/SDL_GameControllerDB.h' || true)
|
||||
|
||||
if [ -z "$staged" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||
|
||||
# Reindent a copy of each file's staged content and see whether it moves.
|
||||
needs=''
|
||||
for f in $staged; do
|
||||
copy="$tmp/$(printf '%s' "$f" | tr '/' '_')"
|
||||
git show ":$f" >"$copy"
|
||||
cp "$copy" "$copy.orig"
|
||||
if ! "$reindent" "$copy" >/dev/null 2>&1; then
|
||||
echo "pre-commit: reindent failed on $f; commit aborted" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! cmp -s "$copy" "$copy.orig"; then
|
||||
needs="$needs $f"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$needs" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Refuse to touch anything if even one file is partially staged.
|
||||
blocked=''
|
||||
for f in $needs; do
|
||||
if ! git diff --quiet -- "$f"; then
|
||||
blocked="$blocked $f"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$blocked" ]; then
|
||||
echo "pre-commit: these files need reindenting but have unstaged changes," >&2
|
||||
echo "so re-staging them would pull unstaged work into the commit:" >&2
|
||||
for f in $blocked; do echo " $f" >&2; done
|
||||
echo >&2
|
||||
echo "Reindent and stage them yourself, then commit again:" >&2
|
||||
echo " scripts/reindent.sh$(for f in $blocked; do printf ' %s' "$f"; done)" >&2
|
||||
echo " git add$(for f in $blocked; do printf ' %s' "$f"; done)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Safe: for every file needing work, the index and working tree agree.
|
||||
# shellcheck disable=SC2086
|
||||
"$reindent" $needs
|
||||
# shellcheck disable=SC2086
|
||||
git add $needs
|
||||
|
||||
echo "pre-commit: reindented and re-staged:" >&2
|
||||
for f in $needs; do echo " $f" >&2; done
|
||||
|
||||
exit 0
|
||||
87
scripts/reindent.el
Normal file
87
scripts/reindent.el
Normal file
@@ -0,0 +1,87 @@
|
||||
;;; reindent.el --- batch reindent to cc-mode "stroustrup" -*- lexical-binding: t -*-
|
||||
|
||||
;; Reindents each file named on the command line, in place, to the project's
|
||||
;; canonical style: cc-mode "stroustrup", 4 columns per level, tabs 8 columns
|
||||
;; wide. See AGENTS.md -> "Coding Style".
|
||||
;;
|
||||
;; The style is set explicitly here rather than read from .dir-locals.el so that
|
||||
;; the result does not depend on where the file lives -- the pre-commit hook
|
||||
;; runs this over temporary copies outside the project tree.
|
||||
;;
|
||||
;; Usage: emacs --batch -Q -l scripts/reindent.el -- FILE...
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cc-mode)
|
||||
|
||||
(setq make-backup-files nil
|
||||
create-lockfiles nil
|
||||
auto-save-default nil
|
||||
vc-handled-backends nil
|
||||
inhibit-message t)
|
||||
|
||||
(defun akgl-retab-leading-whitespace ()
|
||||
"Rewrite every line's leading whitespace as tabs-then-spaces at `tab-width'.
|
||||
|
||||
`indent-region' fixes the column a line starts at, but `indent-line-to' leaves
|
||||
a line alone when it is already at the right column even if the bytes are
|
||||
wrong -- eight spaces where the canonical form is one tab. This pass closes
|
||||
that gap.
|
||||
|
||||
Deliberately not `tabify': that function's `tabify-regexp' is \" [ \\t]+\",
|
||||
which is NOT anchored to the line start, so it rewrites runs of spaces
|
||||
anywhere on the line and destroys the hand-aligned columns in the bit-flag
|
||||
tables in actor.h and iterator.h. Only leading whitespace is touched here, and
|
||||
lines beginning inside a string literal are skipped outright."
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(let ((bol (point)))
|
||||
(unless (nth 3 (syntax-ppss bol))
|
||||
(skip-chars-forward " \t" (line-end-position))
|
||||
(let ((col (current-column)))
|
||||
(unless (or (zerop col) (eolp))
|
||||
(let ((want (concat (make-string (/ col tab-width) ?\t)
|
||||
(make-string (% col tab-width) ?\s))))
|
||||
(unless (string= want (buffer-substring bol (point)))
|
||||
(delete-region bol (point))
|
||||
(insert want)))))))
|
||||
(forward-line 1)))
|
||||
|
||||
(defun akgl-reindent-file (path)
|
||||
"Reindent PATH in place. Returns t if the file changed on disk."
|
||||
(let ((before (with-temp-buffer
|
||||
(insert-file-contents path)
|
||||
(buffer-string))))
|
||||
(with-current-buffer (find-file-noselect path t)
|
||||
(c-mode)
|
||||
(c-set-style "stroustrup")
|
||||
(setq indent-tabs-mode t
|
||||
tab-width 8
|
||||
c-basic-offset 4
|
||||
require-final-newline t)
|
||||
;; 1. Put every line at its correct column.
|
||||
(indent-region (point-min) (point-max))
|
||||
;; 2. Convert leading whitespace to the canonical tab/space mix.
|
||||
(akgl-retab-leading-whitespace)
|
||||
;; 3. Trailing whitespace and a single final newline.
|
||||
(delete-trailing-whitespace)
|
||||
(goto-char (point-max))
|
||||
(unless (bolp) (insert "\n"))
|
||||
(let ((changed (not (string= before (buffer-string)))))
|
||||
(when changed (save-buffer))
|
||||
(kill-buffer)
|
||||
changed))))
|
||||
|
||||
;; Emacs leaves the "--" separator in `command-line-args-left'; drop it, along
|
||||
;; with any empty argument, so the remainder is exactly the file list.
|
||||
(dolist (path (seq-remove (lambda (a) (or (string= a "--") (string= a "")))
|
||||
command-line-args-left))
|
||||
(when (akgl-reindent-file path)
|
||||
(princ (format "reindented %s\n" path))))
|
||||
|
||||
;; Exit 0 on success whether or not anything changed, so that any non-zero
|
||||
;; status from this script means a real failure. Callers detect "something
|
||||
;; changed" from stdout, or by comparing files themselves.
|
||||
(kill-emacs 0)
|
||||
|
||||
;;; reindent.el ends here
|
||||
101
scripts/reindent.sh
Executable file
101
scripts/reindent.sh
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# Reindent C sources to the project's canonical style (cc-mode "stroustrup",
|
||||
# 4-column offset, 8-column tabs). See AGENTS.md -> "Coding Style".
|
||||
#
|
||||
# scripts/reindent.sh reindent every tracked C source in place
|
||||
# scripts/reindent.sh FILE... reindent only the named files
|
||||
# scripts/reindent.sh --check ... report non-conforming files, change nothing
|
||||
#
|
||||
# Exit status: 0 if everything already conforms (or was reindented), 1 if
|
||||
# --check found a file that needs reindenting, 2 on a usage or environment
|
||||
# error.
|
||||
|
||||
set -eu
|
||||
|
||||
root=$(git rev-parse --show-toplevel)
|
||||
elisp="$root/scripts/reindent.el"
|
||||
|
||||
# Directories whose C sources are hand-maintained. Anything outside these is
|
||||
# vendored (deps/) or generated (include/akgl/SDL_GameControllerDB.h) and is
|
||||
# left alone.
|
||||
SCOPE='src include tests util'
|
||||
GENERATED='include/akgl/SDL_GameControllerDB.h'
|
||||
|
||||
check=0
|
||||
if [ "${1:-}" = "--check" ]; then
|
||||
check=1
|
||||
shift
|
||||
fi
|
||||
|
||||
if ! command -v emacs >/dev/null 2>&1; then
|
||||
echo "reindent: emacs not found; cannot verify or apply the canonical style" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ ! -f "$elisp" ]; then
|
||||
echo "reindent: missing $elisp" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Build the file list: explicit arguments, or every tracked C source in scope.
|
||||
if [ "$#" -gt 0 ]; then
|
||||
files=$(for f in "$@"; do printf '%s\n' "$f"; done)
|
||||
else
|
||||
files=$(cd "$root" && git ls-files $SCOPE | grep -E '\.[ch]$' || true)
|
||||
fi
|
||||
|
||||
# Drop generated files and anything that no longer exists on disk.
|
||||
files=$(printf '%s\n' "$files" | grep -v -x -F "$GENERATED" || true)
|
||||
files=$(cd "$root" && for f in $files; do [ -f "$f" ] && printf '%s\n' "$f"; done)
|
||||
|
||||
if [ -z "$files" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$check" -eq 0 ]; then
|
||||
# Reindent in place. A non-zero status here is a real failure -- never
|
||||
# swallow it, or a broken Emacs would look like "everything already conforms".
|
||||
# shellcheck disable=SC2086
|
||||
(cd "$root" && emacs --batch -Q -l "$elisp" -- $files) || {
|
||||
echo "reindent: emacs failed; no files were reindented" >&2
|
||||
exit 2
|
||||
}
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --check: reindent throwaway copies and report which originals differ. The
|
||||
# copies keep their extension so cc-mode still selects the right major mode.
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||
|
||||
# Accept both repo-relative and absolute paths: the default file list is
|
||||
# relative, but callers (including the pre-commit hook) pass absolute ones.
|
||||
abspath() {
|
||||
case "$1" in
|
||||
/*) printf '%s' "$1" ;;
|
||||
*) printf '%s/%s' "$root" "$1" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
copies=''
|
||||
for f in $files; do
|
||||
dest="$tmp/$(printf '%s' "$f" | tr '/' '_')"
|
||||
cp "$(abspath "$f")" "$dest"
|
||||
copies="$copies $dest"
|
||||
done
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
emacs --batch -Q -l "$elisp" -- $copies >/dev/null || {
|
||||
echo "reindent: emacs failed; cannot determine whether sources conform" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
status=0
|
||||
for f in $files; do
|
||||
dest="$tmp/$(printf '%s' "$f" | tr '/' '_')"
|
||||
if ! cmp -s "$(abspath "$f")" "$dest"; then
|
||||
echo "$f"
|
||||
status=1
|
||||
fi
|
||||
done
|
||||
exit $status
|
||||
52
src/actor.c
52
src/actor.c
@@ -28,14 +28,14 @@ akerr_ErrorContext *akgl_actor_initialize(akgl_Actor *obj, char *name)
|
||||
obj->curSpriteReversing = false;
|
||||
obj->scale = 1.0;
|
||||
obj->movement_controls_face = true;
|
||||
|
||||
|
||||
obj->updatefunc = &akgl_actor_update;
|
||||
obj->renderfunc = &akgl_actor_render;
|
||||
obj->facefunc = &akgl_actor_automatic_face;
|
||||
obj->movementlogicfunc = &akgl_actor_logic_movement;
|
||||
obj->changeframefunc = &akgl_actor_logic_changeframe;
|
||||
obj->addchild = &akgl_actor_add_child;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_SetPointerProperty(AKGL_REGISTRY_ACTOR, name, (void *)obj),
|
||||
@@ -111,7 +111,7 @@ akerr_ErrorContext *akgl_actor_logic_changeframe(akgl_Actor *obj, akgl_Sprite *c
|
||||
} else {
|
||||
// we are at the end of the animation and we either loop forward or do not loop
|
||||
obj->curSpriteFrameId = 0;
|
||||
}
|
||||
}
|
||||
// we are not looping in reverse and we are not at the end of the animation
|
||||
} else {
|
||||
obj->curSpriteFrameId += 1;
|
||||
@@ -149,7 +149,7 @@ akerr_ErrorContext *akgl_actor_update(akgl_Actor *obj)
|
||||
PREPARE_ERROR(errctx);
|
||||
SDL_Time curtime = 0;
|
||||
akgl_Sprite *curSprite = NULL;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor reference");
|
||||
FAIL_ZERO_RETURN(errctx, obj->basechar, AKERR_NULLPOINTER, "Actor has NULL base character reference");
|
||||
|
||||
@@ -169,7 +169,7 @@ akerr_ErrorContext *akgl_actor_update(akgl_Actor *obj)
|
||||
// or changeframefunc, both of which should never return AKERR_KEY...
|
||||
SUCCEED_RETURN(errctx);
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -186,10 +186,10 @@ static akerr_ErrorContext *actor_visible(akgl_Actor *obj, SDL_FRect *camera, boo
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_Sprite *curSprite = NULL;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
FAIL_ZERO_RETURN(errctx, obj->basechar, AKERR_NULLPOINTER, "Actor has NULL base character reference");
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_character_sprite_get(obj->basechar, obj->state, &curSprite));
|
||||
} CLEANUP {
|
||||
@@ -199,7 +199,7 @@ static akerr_ErrorContext *actor_visible(akgl_Actor *obj, SDL_FRect *camera, boo
|
||||
*visible = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
if ( (obj->x < (camera->x - curSprite->width)) ||
|
||||
(obj->x > (camera->x + camera->w)) ||
|
||||
(obj->y < (camera->y - curSprite->height)) ||
|
||||
@@ -218,10 +218,10 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
bool visible = false;
|
||||
SDL_FRect src;
|
||||
SDL_FRect dest;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL actor");
|
||||
FAIL_ZERO_RETURN(errctx, obj->basechar, AKERR_NULLPOINTER, "Actor has NULL base character reference");
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_character_sprite_get(obj->basechar, obj->state, &curSprite));
|
||||
CATCH(errctx, actor_visible(obj, camera, &visible));
|
||||
@@ -232,7 +232,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
// If an actor doesn't have a sprite for a state, just log it and move on
|
||||
LOG_ERROR(errctx);
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
if ( ! visible ) {
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -254,7 +254,7 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
if ( obj->parent != NULL ) {
|
||||
dest.x = (obj->parent->x + obj->x - camera->x);
|
||||
dest.y = (obj->parent->y + obj->y - camera->y);
|
||||
@@ -271,21 +271,21 @@ akerr_ErrorContext *akgl_actor_render(akgl_Actor *obj)
|
||||
|
||||
akerr_ErrorContext *akgl_actor_add_child(akgl_Actor *obj, akgl_Actor *child)
|
||||
{
|
||||
int i = 0;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL parent pointer");
|
||||
FAIL_ZERO_RETURN(errctx, child, AKERR_NULLPOINTER, "NULL child pointer");
|
||||
|
||||
FAIL_NONZERO_RETURN(errctx, child->parent, AKERR_RELATIONSHIP, "Child object already has a parent");
|
||||
for ( i = 0; i < AKGL_ACTOR_MAX_CHILDREN ; i++ ) {
|
||||
if ( obj->children[i] == NULL ) {
|
||||
obj->children[i] = child;
|
||||
child->parent = obj;
|
||||
child->refcount += 1;
|
||||
SUCCEED_RETURN(errctx);
|
||||
int i = 0;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL parent pointer");
|
||||
FAIL_ZERO_RETURN(errctx, child, AKERR_NULLPOINTER, "NULL child pointer");
|
||||
|
||||
FAIL_NONZERO_RETURN(errctx, child->parent, AKERR_RELATIONSHIP, "Child object already has a parent");
|
||||
for ( i = 0; i < AKGL_ACTOR_MAX_CHILDREN ; i++ ) {
|
||||
if ( obj->children[i] == NULL ) {
|
||||
obj->children[i] = child;
|
||||
child->parent = obj;
|
||||
child->refcount += 1;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Parent object has no remaining child slots left");
|
||||
FAIL_RETURN(errctx, AKERR_OUTOFBOUNDS, "Parent object has no remaining child slots left");
|
||||
}
|
||||
|
||||
void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, const char *name)
|
||||
|
||||
76
src/assets.c
76
src/assets.c
@@ -14,46 +14,46 @@
|
||||
|
||||
akerr_ErrorContext *akgl_load_start_bgm(char *fname)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
//akgl_String *tmpstr = NULL;
|
||||
MIX_Track *bgmtrack = NULL;
|
||||
SDL_PropertiesID bgmprops = 0;
|
||||
PREPARE_ERROR(errctx);
|
||||
//akgl_String *tmpstr = NULL;
|
||||
MIX_Track *bgmtrack = NULL;
|
||||
SDL_PropertiesID bgmprops = 0;
|
||||
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(errctx, fname, AKERR_NULLPOINTER, "akgl_load_start_bgm received NULL filename");
|
||||
//CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
//CATCH(errctx, akgl_string_initialize(tmpstr, NULL));
|
||||
|
||||
//SDL_snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname);
|
||||
SDL_Log("Loading music asset from %s", fname);
|
||||
bgm = MIX_LoadAudio(akgl_mixer, fname, true);
|
||||
FAIL_ZERO_BREAK(errctx, bgm, AKERR_NULLPOINTER, "Failed to load music asset %s : %s", fname, SDL_GetError());
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(errctx, fname, AKERR_NULLPOINTER, "akgl_load_start_bgm received NULL filename");
|
||||
//CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
//CATCH(errctx, akgl_string_initialize(tmpstr, NULL));
|
||||
|
||||
bgmtrack = MIX_CreateTrack(akgl_mixer);
|
||||
FAIL_ZERO_BREAK(errctx, bgmtrack, AKERR_NULLPOINTER, "Failed to create audio track for background music: %s", SDL_GetError());
|
||||
//SDL_snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname);
|
||||
SDL_Log("Loading music asset from %s", fname);
|
||||
bgm = MIX_LoadAudio(akgl_mixer, fname, true);
|
||||
FAIL_ZERO_BREAK(errctx, bgm, AKERR_NULLPOINTER, "Failed to load music asset %s : %s", fname, SDL_GetError());
|
||||
|
||||
akgl_tracks[AKGL_GAME_AUDIO_TRACK_BGM] = bgmtrack;
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
MIX_SetTrackAudio(bgmtrack, bgm),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
bgmtrack = MIX_CreateTrack(akgl_mixer);
|
||||
FAIL_ZERO_BREAK(errctx, bgmtrack, AKERR_NULLPOINTER, "Failed to create audio track for background music: %s", SDL_GetError());
|
||||
|
||||
SDL_SetNumberProperty(bgmprops, MIX_PROP_PLAY_LOOPS_NUMBER, -1);
|
||||
|
||||
if (!MIX_PlayTrack(bgmtrack, bgmprops)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Failed to play music asset %s", fname);
|
||||
}
|
||||
} CLEANUP {
|
||||
//IGNORE(akgl_heap_release_string(tmpstr));
|
||||
if ( errctx != NULL ) {
|
||||
if ( errctx->status != 0 && bgm != NULL) {
|
||||
MIX_DestroyAudio(bgm);
|
||||
}
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
akgl_tracks[AKGL_GAME_AUDIO_TRACK_BGM] = bgmtrack;
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
MIX_SetTrackAudio(bgmtrack, bgm),
|
||||
AKGL_ERR_SDL,
|
||||
"%s",
|
||||
SDL_GetError());
|
||||
|
||||
SDL_SetNumberProperty(bgmprops, MIX_PROP_PLAY_LOOPS_NUMBER, -1);
|
||||
|
||||
if (!MIX_PlayTrack(bgmtrack, bgmprops)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Failed to play music asset %s", fname);
|
||||
}
|
||||
} CLEANUP {
|
||||
//IGNORE(akgl_heap_release_string(tmpstr));
|
||||
if ( errctx != NULL ) {
|
||||
if ( errctx->status != 0 && bgm != NULL) {
|
||||
MIX_DestroyAudio(bgm);
|
||||
}
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ akerr_ErrorContext *akgl_character_initialize(akgl_Character *obj, char *name)
|
||||
strncpy(obj->name, name, AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH);
|
||||
obj->state_sprites = SDL_CreateProperties();
|
||||
FAIL_ZERO_RETURN(errctx, obj->state_sprites, AKERR_NULLPOINTER, "Unable to initialize SDL_PropertiesID for character state map");
|
||||
|
||||
|
||||
obj->sprite_add = &akgl_character_sprite_add;
|
||||
obj->sprite_get = &akgl_character_sprite_get;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(
|
||||
errctx,
|
||||
SDL_SetPointerProperty(AKGL_REGISTRY_CHARACTER, name, (void *)obj),
|
||||
@@ -106,7 +106,7 @@ static akerr_ErrorContext *akgl_character_load_json_state_int_from_strings(json_
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, states, AKERR_NULLPOINTER, "NULL states array");
|
||||
FAIL_ZERO_RETURN(errctx, states, AKERR_NULLPOINTER, "NULL destination integer");
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstring));
|
||||
for ( i = 0; i < json_array_size((json_t *)states) ; i++ ) {
|
||||
@@ -140,7 +140,7 @@ static akerr_ErrorContext *akgl_character_load_json_inner(json_t *json, akgl_Cha
|
||||
akgl_String *tmpstr = NULL;
|
||||
akgl_String *tmpstr2 = NULL;
|
||||
int stateval = 0;
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &tmpstr));
|
||||
CATCH(errctx, akgl_character_initialize((akgl_Character *)obj, tmpstr->data));
|
||||
@@ -155,15 +155,15 @@ static akerr_ErrorContext *akgl_character_load_json_inner(json_t *json, akgl_Cha
|
||||
NULL
|
||||
);
|
||||
CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &tmpstr2));
|
||||
|
||||
|
||||
CATCH(errctx, akgl_get_json_array_value((json_t *)curmapping, "state", &statearray));
|
||||
CATCH(errctx, akgl_character_load_json_state_int_from_strings(statearray, &stateval));
|
||||
|
||||
|
||||
CATCH(errctx, akgl_get_json_string_value((json_t *)curmapping, "sprite", &tmpstr));
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
spriteptr,
|
||||
AKERR_NULLPOINTER,
|
||||
AKERR_NULLPOINTER,
|
||||
"Character %s for state %b references sprite %s but not found in the registry",
|
||||
tmpstr2->data,
|
||||
stateval,
|
||||
@@ -190,7 +190,7 @@ akerr_ErrorContext *akgl_character_load_json(char *filename)
|
||||
json_error_t error;
|
||||
akgl_Character *obj = NULL;
|
||||
//akgl_String *tmpstr = NULL;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, filename, AKERR_NULLPOINTER, "Received null filename");
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_character(&obj));
|
||||
|
||||
@@ -19,7 +19,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_list_keyboards(void)
|
||||
PREPARE_ERROR(e);
|
||||
|
||||
FAIL_ZERO_RETURN(e, keyboards, AKERR_NULLPOINTER, "%s", SDL_GetError());
|
||||
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char *name = SDL_GetKeyboardNameForID(keyboards[i]);
|
||||
SDL_Log("Keyboard %d: ID %u, Name: %s\n", i, keyboards[i], name);
|
||||
@@ -80,7 +80,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
|
||||
// This controlmap processes this control
|
||||
eventButtonComboMatch = (
|
||||
((event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ||
|
||||
event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) &&
|
||||
event->type == SDL_EVENT_GAMEPAD_BUTTON_UP) &&
|
||||
event->gbutton.which == curmap->jsid &&
|
||||
event->gbutton.button == curcontrol->button) ||
|
||||
((event->type == SDL_EVENT_KEY_DOWN ||
|
||||
@@ -104,7 +104,7 @@ akerr_ErrorContext *akgl_controller_handle_event(void *appstate, SDL_Event *even
|
||||
}
|
||||
}
|
||||
}
|
||||
_akgl_controller_handle_event_success:
|
||||
_akgl_controller_handle_event_success:
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -121,7 +121,7 @@ _akgl_controller_handle_event_success:
|
||||
akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event)
|
||||
{
|
||||
akgl_Actor *player = NULL;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate");
|
||||
@@ -130,39 +130,39 @@ akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event)
|
||||
FAIL_ZERO_RETURN(errctx, player, AKERR_NULLPOINTER, "Player actor does not exist");
|
||||
|
||||
if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN ||
|
||||
event->key.key == SDLK_DOWN ) {
|
||||
event->key.key == SDLK_DOWN ) {
|
||||
SDL_Log("Processing dpad down : state %d", player->state);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_DOWN);
|
||||
if ( !player->movement_controls_face ) {
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_DOWN);
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_DOWN);
|
||||
}
|
||||
SDL_Log("New state : %d", player->state);
|
||||
} else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP ||
|
||||
event->key.key == SDLK_UP ) {
|
||||
event->key.key == SDLK_UP ) {
|
||||
SDL_Log("Processing dpad up");
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_UP);
|
||||
if ( !player->movement_controls_face ) {
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_UP);
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_UP);
|
||||
}
|
||||
SDL_Log("New state : %d", player->state);
|
||||
} else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT ||
|
||||
event->key.key == SDLK_LEFT ) {
|
||||
event->key.key == SDLK_LEFT ) {
|
||||
SDL_Log("Processing dpad left");
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_LEFT);
|
||||
if ( !player->movement_controls_face ) {
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
}
|
||||
SDL_Log("New state : %d", player->state);
|
||||
} else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT ||
|
||||
event->key.key == SDLK_RIGHT ) {
|
||||
event->key.key == SDLK_RIGHT ) {
|
||||
SDL_Log("Processing dpad right");
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT);
|
||||
if ( !player->movement_controls_face ) {
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_RIGHT);
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
AKGL_BITMASK_ADD(player->state, AKGL_ACTOR_STATE_FACE_RIGHT);
|
||||
}
|
||||
SDL_Log("New state : %d", player->state);
|
||||
}
|
||||
@@ -179,7 +179,7 @@ akerr_ErrorContext *gamepad_handle_button_down(void *appstate, SDL_Event *event)
|
||||
akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event)
|
||||
{
|
||||
akgl_Actor *player = NULL;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate");
|
||||
@@ -188,25 +188,25 @@ akerr_ErrorContext *gamepad_handle_button_up(void *appstate, SDL_Event *event)
|
||||
FAIL_ZERO_RETURN(errctx, player, AKERR_NULLPOINTER, "Player actor does not exist");
|
||||
|
||||
if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_DOWN ||
|
||||
event->key.key == SDLK_DOWN ) {
|
||||
event->key.key == SDLK_DOWN ) {
|
||||
SDL_Log("processing down release");
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_DOWN);
|
||||
player->curSpriteFrameId = 0;
|
||||
SDL_Log("New state : %d", player->state);
|
||||
} else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_UP ||
|
||||
event->key.key == SDLK_UP ) {
|
||||
event->key.key == SDLK_UP ) {
|
||||
SDL_Log("processing up release");
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_UP);
|
||||
player->curSpriteFrameId = 0;
|
||||
SDL_Log("New state : %d", player->state);
|
||||
} else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_RIGHT ||
|
||||
event->key.key == SDLK_RIGHT) {
|
||||
event->key.key == SDLK_RIGHT) {
|
||||
SDL_Log("processing right release");
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_RIGHT);
|
||||
player->curSpriteFrameId = 0;
|
||||
SDL_Log("New state : %d", player->state);
|
||||
} else if ( event->gbutton.button == SDL_GAMEPAD_BUTTON_DPAD_LEFT ||
|
||||
event->key.key == SDLK_LEFT ) {
|
||||
event->key.key == SDLK_LEFT ) {
|
||||
SDL_Log("processing left release");
|
||||
AKGL_BITMASK_DEL(player->state, AKGL_ACTOR_STATE_MOVING_LEFT);
|
||||
player->curSpriteFrameId = 0;
|
||||
@@ -227,24 +227,24 @@ akerr_ErrorContext *gamepad_handle_added(void *appstate, SDL_Event *event)
|
||||
SDL_JoystickID which;
|
||||
SDL_Gamepad *gamepad = NULL;
|
||||
char *mapping = NULL;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate");
|
||||
FAIL_ZERO_RETURN(errctx, event, AKERR_NULLPOINTER, "NULL event");
|
||||
|
||||
|
||||
which = event->gbutton.which;
|
||||
gamepad = SDL_GetGamepadFromID(which);
|
||||
|
||||
|
||||
if (!gamepad) {
|
||||
SDL_Log("Gamepad #%u add, but not opened: %s", (unsigned int) which, SDL_GetError());
|
||||
gamepad = SDL_OpenGamepad(which);
|
||||
SDL_Log("Gamepad #%u opened: %s", (unsigned int) which, SDL_GetError());
|
||||
mapping = SDL_GetGamepadMapping(gamepad);
|
||||
if ( mapping == NULL ) {
|
||||
SDL_Log("Gamepad #%u has no mapping!", (unsigned int) which);
|
||||
SDL_Log("Gamepad #%u has no mapping!", (unsigned int) which);
|
||||
} else if ( mapping != NULL ) {
|
||||
SDL_Log("Gamepad #%u mapping : %s", (unsigned int) which, mapping);
|
||||
SDL_free(mapping);
|
||||
SDL_Log("Gamepad #%u mapping : %s", (unsigned int) which, mapping);
|
||||
SDL_free(mapping);
|
||||
}
|
||||
} else {
|
||||
SDL_Log("Gamepad #%u ('%s') added", (unsigned int) which, SDL_GetGamepadName(gamepad));
|
||||
@@ -270,7 +270,7 @@ akerr_ErrorContext *gamepad_handle_removed(void *appstate, SDL_Event *event)
|
||||
|
||||
which = event->gbutton.which;
|
||||
gamepad = SDL_GetGamepadFromID(which);
|
||||
|
||||
|
||||
if (gamepad) {
|
||||
SDL_CloseGamepad(gamepad); /* the joystick was unplugged. */
|
||||
}
|
||||
@@ -292,14 +292,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_pushmap(int controlmapid, akg
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, char *actorname, int kbid, int jsid)
|
||||
{
|
||||
akgl_ControlMap *controlmap;
|
||||
akgl_Control control;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
// set up the control map
|
||||
@@ -308,12 +308,12 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
controlmap = &GAME_ControlMaps[controlmapid];
|
||||
controlmap->kbid = kbid;
|
||||
controlmap->jsid = jsid;
|
||||
|
||||
|
||||
controlmap->target = SDL_GetPointerProperty(AKGL_REGISTRY_ACTOR, actorname, NULL);
|
||||
FAIL_ZERO_BREAK(errctx, controlmap->target, AKGL_ERR_REGISTRY, "Actor %s not found in registry", actorname);
|
||||
|
||||
// ---- KEYBOARD CONTROLS ----
|
||||
|
||||
|
||||
// Move down
|
||||
control.key = SDLK_DOWN;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
@@ -329,7 +329,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
control.handler_on = &akgl_Actor_cmhf_up_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_up_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
|
||||
// Move left
|
||||
control.key = SDLK_LEFT;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
@@ -337,7 +337,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_left_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
|
||||
// Move right
|
||||
control.key = SDLK_RIGHT;
|
||||
control.event_on = SDL_EVENT_KEY_DOWN;
|
||||
@@ -363,7 +363,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
control.handler_on = &akgl_Actor_cmhf_up_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_up_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
|
||||
// Move left
|
||||
control.button = SDL_GAMEPAD_BUTTON_DPAD_LEFT;
|
||||
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
||||
@@ -371,7 +371,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
control.handler_on = &akgl_Actor_cmhf_left_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_left_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
|
||||
// Move right
|
||||
control.button = SDL_GAMEPAD_BUTTON_DPAD_RIGHT;
|
||||
control.event_on = SDL_EVENT_GAMEPAD_BUTTON_DOWN;
|
||||
@@ -379,9 +379,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_controller_default(int controlmapid, cha
|
||||
control.handler_on = &akgl_Actor_cmhf_right_on;
|
||||
control.handler_off = &akgl_Actor_cmhf_right_off;
|
||||
CATCH(errctx, akgl_controller_pushmap(controlmapid, &control));
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
20
src/draw.c
20
src/draw.c
@@ -12,8 +12,8 @@
|
||||
void akgl_draw_background(int w, int h)
|
||||
{
|
||||
SDL_Color col[2] = {
|
||||
{ 0x66, 0x66, 0x66, 0xff },
|
||||
{ 0x99, 0x99, 0x99, 0xff },
|
||||
{ 0x66, 0x66, 0x66, 0xff },
|
||||
{ 0x99, 0x99, 0x99, 0xff },
|
||||
};
|
||||
int i, x, y;
|
||||
SDL_FRect rect;
|
||||
@@ -22,14 +22,14 @@ void akgl_draw_background(int w, int h)
|
||||
rect.w = (float)dx;
|
||||
rect.h = (float)dy;
|
||||
for (y = 0; y < h; y += dy) {
|
||||
for (x = 0; x < w; x += dx) {
|
||||
/* use an 8x8 checkerboard pattern */
|
||||
i = (((x ^ y) >> 3) & 1);
|
||||
SDL_SetRenderDrawColor(renderer->sdl_renderer, col[i].r, col[i].g, col[i].b, col[i].a);
|
||||
for (x = 0; x < w; x += dx) {
|
||||
/* use an 8x8 checkerboard pattern */
|
||||
i = (((x ^ y) >> 3) & 1);
|
||||
SDL_SetRenderDrawColor(renderer->sdl_renderer, col[i].r, col[i].g, col[i].b, col[i].a);
|
||||
|
||||
rect.x = (float)x;
|
||||
rect.y = (float)y;
|
||||
SDL_RenderFillRect(renderer->sdl_renderer, &rect);
|
||||
}
|
||||
rect.x = (float)x;
|
||||
rect.y = (float)y;
|
||||
SDL_RenderFillRect(renderer->sdl_renderer, &rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
src/error.c
Normal file
24
src/error.c
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @file error.c
|
||||
* @brief Implements the error subsystem: claims and names the libakgl status band.
|
||||
*/
|
||||
|
||||
#include <akerror.h>
|
||||
|
||||
#include <akgl/error.h>
|
||||
|
||||
akerr_ErrorContext *akgl_error_init(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
// Claim the whole band before naming anything in it: libakerror refuses a
|
||||
// name for a status we do not own. Any collision propagates to the caller
|
||||
// -- another component owning part of our range is an initialization
|
||||
// failure, not a warning.
|
||||
PASS(errctx, akerr_reserve_status_range(AKGL_ERR_BASE, AKGL_ERR_COUNT, AKGL_ERR_OWNER));
|
||||
PASS(errctx, akerr_register_status_name(AKGL_ERR_OWNER, AKGL_ERR_SDL, "SDL Error"));
|
||||
PASS(errctx, akerr_register_status_name(AKGL_ERR_OWNER, AKGL_ERR_REGISTRY, "Registry Error"));
|
||||
PASS(errctx, akerr_register_status_name(AKGL_ERR_OWNER, AKGL_ERR_HEAP, "Heap Error"));
|
||||
PASS(errctx, akerr_register_status_name(AKGL_ERR_OWNER, AKGL_ERR_BEHAVIOR, "Behavior Error"));
|
||||
PASS(errctx, akerr_register_status_name(AKGL_ERR_OWNER, AKGL_ERR_LOGICINTERRUPT, "Logic Interrupt"));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
83
src/game.c
83
src/game.c
@@ -21,6 +21,7 @@
|
||||
#include <akgl/staticstring.h>
|
||||
#include <akgl/iterator.h>
|
||||
#include <akgl/physics.h>
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/SDL_GameControllerDB.h>
|
||||
|
||||
SDL_Window *window = NULL;
|
||||
@@ -52,9 +53,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
||||
{
|
||||
int screenwidth = 0;
|
||||
int screenheight = 0;
|
||||
|
||||
|
||||
int i = 0;
|
||||
PREPARE_ERROR(e);
|
||||
// First, before anything that can raise: everything below reports through
|
||||
// AKGL_ERR_* codes, and a code raised before its name is registered prints
|
||||
// as "Unknown Error" in the stack trace the caller is left holding.
|
||||
PASS(e, akgl_error_init());
|
||||
strncpy((char *)&game.libversion, AKGL_VERSION, 32);
|
||||
game.gameStartTime = SDL_GetTicksNS();
|
||||
game.lastIterTime = game.gameStartTime;
|
||||
@@ -75,13 +80,13 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
||||
PASS(e, akgl_registry_init_music());
|
||||
PASS(e, akgl_registry_init_properties());
|
||||
PASS(e, akgl_registry_init_actor_state_strings());
|
||||
|
||||
|
||||
SDL_SetAppMetadata(game.name, game.version, game.uri);
|
||||
|
||||
for ( i = 0 ; i < AKGL_MAX_CONTROL_MAPS; i++ ) {
|
||||
memset(&GAME_ControlMaps[i], 0x00, sizeof(akgl_ControlMap));
|
||||
}
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(
|
||||
e,
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD | SDL_INIT_AUDIO),
|
||||
@@ -123,7 +128,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_init()
|
||||
renderer = &_akgl_renderer;
|
||||
physics = &_akgl_physics;
|
||||
gamemap = &_akgl_gamemap;
|
||||
|
||||
|
||||
PASS(e, akgl_game_state_unlock());
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
@@ -358,27 +363,27 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_objectnamemap(FILE *fp, SDL_Pr
|
||||
// corrupt name table as a successful load.
|
||||
while ( done == false ) {
|
||||
ATTEMPT {
|
||||
CATCH(e, aksl_fread((void *)&objname, 1, namelength, fp));
|
||||
CATCH(e, aksl_fread((void *)&ptr, 1, ptrlength, fp));
|
||||
// End of the map
|
||||
if ( ptr == 0x00 && objname[0] == 0x00 ) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
// The map allows us to say "Object X has a reference to object Y at
|
||||
// address Z. The object they had at address Z was named A. Our current
|
||||
// instance of object named A is at address B. So we map address Z to
|
||||
// address B, so that we can reconnect function pointers on objects loaded
|
||||
// from the save game state."
|
||||
|
||||
// SDL_Properties objects can only use string keys, so we can't use the
|
||||
// old pointer as a key without first converting it to a string.
|
||||
CATCH(e, aksl_memset((void *)&ptrstring, 0x00, 32));
|
||||
snprintf((char *)&ptrstring, 32, "%p", ptr);
|
||||
SDL_SetPointerProperty(
|
||||
map,
|
||||
ptrstring,
|
||||
SDL_GetPointerProperty(registry, objname, NULL));
|
||||
CATCH(e, aksl_fread((void *)&objname, 1, namelength, fp));
|
||||
CATCH(e, aksl_fread((void *)&ptr, 1, ptrlength, fp));
|
||||
// End of the map
|
||||
if ( ptr == 0x00 && objname[0] == 0x00 ) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
// The map allows us to say "Object X has a reference to object Y at
|
||||
// address Z. The object they had at address Z was named A. Our current
|
||||
// instance of object named A is at address B. So we map address Z to
|
||||
// address B, so that we can reconnect function pointers on objects loaded
|
||||
// from the save game state."
|
||||
|
||||
// SDL_Properties objects can only use string keys, so we can't use the
|
||||
// old pointer as a key without first converting it to a string.
|
||||
CATCH(e, aksl_memset((void *)&ptrstring, 0x00, 32));
|
||||
snprintf((char *)&ptrstring, 32, "%p", ptr);
|
||||
SDL_SetPointerProperty(
|
||||
map,
|
||||
ptrstring,
|
||||
SDL_GetPointerProperty(registry, objname, NULL));
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
@@ -404,9 +409,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load_versioncmp(char *versiontype,
|
||||
FAIL_ZERO_RETURN(e, versiontype, AKERR_NULLPOINTER, "NULL argument");
|
||||
FAIL_ZERO_RETURN(e, curversion, AKERR_NULLPOINTER, "NULL argument");
|
||||
FAIL_ZERO_RETURN(e, newversion, AKERR_NULLPOINTER, "NULL argument");
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
// Check save game library version
|
||||
// Check save game library version
|
||||
FAIL_NONZERO_BREAK(
|
||||
e,
|
||||
semver_parse((const char *)curversion, ¤t_version),
|
||||
@@ -445,10 +450,10 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
||||
SDL_PropertiesID spritesheetmap;
|
||||
SDL_PropertiesID charactermap;
|
||||
FILE *fp = NULL;
|
||||
|
||||
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, fpath, AKERR_NULLPOINTER, "NULL file path");
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(e, aksl_fopen(fpath, "rb", &fp));
|
||||
CATCH(e, aksl_fread((void *)&savegame, 1, sizeof(akgl_Game), fp));
|
||||
@@ -464,28 +469,28 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_load(char *fpath)
|
||||
strncmp((char *)&savegame.uri, (char *)&game.uri, 256),
|
||||
AKERR_API,
|
||||
"Savegame is not compatible with this game");
|
||||
|
||||
|
||||
memcpy((void *)&game, (void *)&savegame, sizeof(akgl_Game));
|
||||
// Load actor name map
|
||||
actormap = SDL_CreateProperties();
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR));
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, actormap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Actor *), AKGL_REGISTRY_ACTOR));
|
||||
// Load sprite name map
|
||||
spritemap = SDL_CreateProperties();
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE));
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, spritemap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Sprite *), AKGL_REGISTRY_SPRITE));
|
||||
// Load spritesheet name map
|
||||
spritesheetmap = SDL_CreateProperties();
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET));
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, spritesheetmap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_SpriteSheet *), AKGL_REGISTRY_SPRITESHEET));
|
||||
// Load character name map
|
||||
charactermap = SDL_CreateProperties();
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER));
|
||||
// Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers
|
||||
CATCH(e, akgl_game_load_objectnamemap(fp, charactermap, AKGL_ACTOR_MAX_NAME_LENGTH, sizeof(akgl_Character *), AKGL_REGISTRY_CHARACTER));
|
||||
// Now that we have all of our pointer maps built, we can load the actual binary objects and reset their pointers
|
||||
} CLEANUP {
|
||||
if ( fp != NULL ) {
|
||||
fclose(fp);
|
||||
}
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -502,7 +507,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags)
|
||||
if ( opflags == NULL ) {
|
||||
opflags = &defflags;
|
||||
}
|
||||
|
||||
|
||||
PASS(e, akgl_game_state_lock());
|
||||
|
||||
akgl_game_updateFPS();
|
||||
@@ -521,8 +526,8 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_game_update(akgl_Iterator *opflags)
|
||||
} else {
|
||||
actor->scale = 1.0;
|
||||
}
|
||||
PASS(e, actor->updatefunc(actor));
|
||||
}
|
||||
PASS(e, actor->updatefunc(actor));
|
||||
}
|
||||
}
|
||||
PASS(e, physics->simulate(physics, NULL));
|
||||
PASS(e, renderer->draw_world(renderer, NULL));
|
||||
|
||||
@@ -24,11 +24,6 @@ akerr_ErrorContext *akgl_heap_init()
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
int i = 0;
|
||||
akerr_name_for_status(AKGL_ERR_SDL, "SDL Error");
|
||||
akerr_name_for_status(AKGL_ERR_REGISTRY, "Registry Error");
|
||||
akerr_name_for_status(AKGL_ERR_HEAP, "Heap Error");
|
||||
akerr_name_for_status(AKGL_ERR_BEHAVIOR, "Behavior Error");
|
||||
akerr_name_for_status(AKGL_ERR_LOGICINTERRUPT, "Logic Interrupt");
|
||||
PASS(errctx, akgl_heap_init_actor());
|
||||
for ( i = 0; i < AKGL_MAX_HEAP_SPRITE; i++) {
|
||||
memset(&HEAP_SPRITE[i], 0x00, sizeof(akgl_Sprite));
|
||||
@@ -146,7 +141,7 @@ akerr_ErrorContext *akgl_heap_release_character(akgl_Character *basechar)
|
||||
akgl_Iterator opflags;
|
||||
FAIL_ZERO_RETURN(errctx, basechar, AKERR_NULLPOINTER, "NULL character reference");
|
||||
AKGL_BITMASK_CLEAR(opflags.flags);
|
||||
|
||||
|
||||
if ( basechar->refcount > 0 ) {
|
||||
basechar->refcount -= 1;
|
||||
}
|
||||
@@ -201,4 +196,3 @@ akerr_ErrorContext *akgl_heap_release_string(akgl_String *ptr)
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,135 +15,135 @@
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_object_value(json_t *obj, char *key, json_t **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_object(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = value;
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_object(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = value;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_boolean_value(json_t *obj, char *key, bool *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_boolean(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_boolean_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_boolean(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_boolean_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_integer_value(json_t *obj, char *key, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_integer(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_integer_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL object pointer");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_integer(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_integer_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_number_value(json_t *obj, char *key, float *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_number_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_number_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_double_value(json_t *obj, char *key, double *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_number_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_number(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = json_number_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_string_value(json_t *obj, char *key, akgl_String **dest)
|
||||
{
|
||||
json_t *value = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = NULL;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
FAIL_ZERO_RETURN(errctx, key, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
|
||||
value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_string(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
ATTEMPT {
|
||||
if ( *dest == NULL ) {
|
||||
CATCH(errctx, akgl_heap_next_string(dest));
|
||||
CATCH(errctx, akgl_string_initialize(*dest, NULL));
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, false);
|
||||
|
||||
strncpy((char *)&(*dest)->data, json_string_value(value), AKGL_MAX_STRING_LENGTH);
|
||||
SUCCEED_RETURN(errctx);
|
||||
value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_string(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
ATTEMPT {
|
||||
if ( *dest == NULL ) {
|
||||
CATCH(errctx, akgl_heap_next_string(dest));
|
||||
CATCH(errctx, akgl_string_initialize(*dest, NULL));
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, false);
|
||||
|
||||
strncpy((char *)&(*dest)->data, json_string_value(value), AKGL_MAX_STRING_LENGTH);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_array_value(json_t *obj, char *key, json_t **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_array(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = value;
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_object_get(obj, key);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_KEY, "Key %s not found in object", key);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_array(value)), AKERR_TYPE, "Key %s in object has incorrect type", key);
|
||||
*dest = value;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_array_index_object(json_t *array, int index, json_t **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_array_get(array, index);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_object(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
|
||||
*dest = value;
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_array_get(array, index);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_object(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
|
||||
*dest = value;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_array_index_integer(json_t *array, int index, int *dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_array_get(array, index);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_integer(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
|
||||
*dest = json_integer_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
json_t *value = json_array_get(array, index);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_integer(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
|
||||
*dest = json_integer_value(value);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_get_json_array_index_string(json_t *array, int index, akgl_String **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer reference");
|
||||
json_t *value = json_array_get(array, index);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_string(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
|
||||
ATTEMPT {
|
||||
if ( *dest == NULL ) {
|
||||
CATCH(errctx, akgl_heap_next_string(dest));
|
||||
CATCH(errctx, akgl_string_initialize(*dest, NULL));
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, false);
|
||||
|
||||
strncpy((char *)&(*dest)->data, json_string_value(value), AKGL_MAX_STRING_LENGTH);
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, array, AKERR_NULLPOINTER, "NULL pointer reference");
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL destination pointer reference");
|
||||
json_t *value = json_array_get(array, index);
|
||||
FAIL_ZERO_RETURN(errctx, value, AKERR_OUTOFBOUNDS, "Index %d out of bounds for array", index);
|
||||
FAIL_ZERO_RETURN(errctx, (json_is_string(value)), AKERR_TYPE, "Index %d in object has incorrect type", index);
|
||||
ATTEMPT {
|
||||
if ( *dest == NULL ) {
|
||||
CATCH(errctx, akgl_heap_next_string(dest));
|
||||
CATCH(errctx, akgl_string_initialize(*dest, NULL));
|
||||
}
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, false);
|
||||
|
||||
strncpy((char *)&(*dest)->data, json_string_value(value), AKGL_MAX_STRING_LENGTH);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext *err, void *defval, void *dest, uint32_t defsize)
|
||||
@@ -153,11 +153,11 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
int docopy = 0;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(e, err, AKERR_NULLPOINTER, "err");
|
||||
FAIL_ZERO_RETURN(e, defval, AKERR_NULLPOINTER, "defval");
|
||||
FAIL_ZERO_RETURN(e, dest, AKERR_NULLPOINTER, "dest");
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
} CLEANUP {
|
||||
} PROCESS(err) {
|
||||
@@ -165,6 +165,6 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_with_default(akerr_ErrorContext
|
||||
} HANDLE_GROUP(err, AKERR_INDEX) {
|
||||
memcpy(dest, defval, defsize);
|
||||
} FINISH(err, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_null(akgl_PhysicsBackend *s
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
||||
|
||||
|
||||
self->gravity = akgl_physics_null_gravity;
|
||||
self->collide = akgl_physics_null_collide;
|
||||
self->move = akgl_physics_null_move;
|
||||
@@ -65,7 +65,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_gravity(akgl_PhysicsBacke
|
||||
// Assume Z origin is - (behind the camera)
|
||||
actor->ez -= (self->gravity_z * dt);
|
||||
}
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_arcade_move(struct akgl_PhysicsB
|
||||
FAIL_ZERO_RETURN(e, actor, AKERR_NULLPOINTER, "actor");
|
||||
actor->x += actor->vx * dt;
|
||||
actor->y += actor->vy * dt;
|
||||
actor->z += actor->vz * dt;
|
||||
actor->z += actor->vz * dt;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
||||
PASS(e, akgl_heap_next_string(&tmp));
|
||||
|
||||
|
||||
self->gravity = akgl_physics_arcade_gravity;
|
||||
self->collide = akgl_physics_arcade_collide;
|
||||
self->move = akgl_physics_arcade_move;
|
||||
@@ -117,7 +117,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_init_arcade(akgl_PhysicsBackend
|
||||
IGNORE(akgl_heap_release_string(tmp));
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
|
||||
if ( opflags == NULL ) {
|
||||
opflags = &defflags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_ACTOR; i++ ) {
|
||||
actor = &HEAP_ACTOR[i];
|
||||
if ( actor->refcount == 0 ) {
|
||||
@@ -164,21 +164,21 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// thrust is a function of acceleration on a given axis
|
||||
if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_LEFT) ||
|
||||
AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) {
|
||||
AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_RIGHT) ) {
|
||||
actor->tx += actor->ax * dt;
|
||||
}
|
||||
if ( AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_UP) ||
|
||||
AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_DOWN) ) {
|
||||
AKGL_BITMASK_HAS(actor->state, AKGL_ACTOR_STATE_MOVING_DOWN) ) {
|
||||
actor->ty += actor->ay * dt;
|
||||
}
|
||||
|
||||
// velocity equals thrust unless thrust exceeds max speed
|
||||
if ( fabsf(actor->tx) > fabsf(actor->sx) ) {
|
||||
if ( actor->tx < 0 ) {
|
||||
actor->tx = -actor->sx;
|
||||
actor->tx = -actor->sx;
|
||||
} else {
|
||||
actor->tx = actor->sx;
|
||||
}
|
||||
@@ -216,14 +216,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_simulate(akgl_PhysicsBackend *se
|
||||
actor->vy = actor->ey + actor->ty;
|
||||
actor->vz = actor->ez + actor->tz;
|
||||
|
||||
PASS(e, self->move(self, actor, dt));
|
||||
PASS(e, self->move(self, actor, dt));
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} HANDLE(e, AKGL_ERR_LOGICINTERRUPT) {
|
||||
// noop
|
||||
} FINISH(e, true);
|
||||
}
|
||||
self->gravity_time = curtime;
|
||||
self->gravity_time = curtime;
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_physics_factory(akgl_PhysicsBackend *sel
|
||||
PREPARE_ERROR(e);
|
||||
FAIL_ZERO_RETURN(e, self, AKERR_NULLPOINTER, "self");
|
||||
FAIL_ZERO_RETURN(e, type, AKERR_NULLPOINTER, "type");
|
||||
|
||||
|
||||
if ( strncmp(type->data, "null", 4) == 0) {
|
||||
PASS(e, akgl_physics_init_null(self));
|
||||
SUCCEED_RETURN(e);
|
||||
|
||||
@@ -95,7 +95,7 @@ akerr_ErrorContext *akgl_registry_init_actor_state_strings()
|
||||
|
||||
akerr_ErrorContext *akgl_registry_init_sprite()
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
AKGL_REGISTRY_SPRITE = SDL_CreateProperties();
|
||||
FAIL_ZERO_RETURN(errctx, AKGL_REGISTRY_SPRITE, AKERR_NULLPOINTER, "Error initializing sprite registry");
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -123,7 +123,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_registry_load_properties(char *fname)
|
||||
json_t *props = NULL;
|
||||
const char *pkey = NULL;
|
||||
json_t *pvalue = NULL;
|
||||
|
||||
|
||||
json_error_t error;
|
||||
akgl_String *tmpstr;
|
||||
|
||||
@@ -189,4 +189,3 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_get_property(char *name, akgl_String **d
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,14 +30,14 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_init2d(akgl_RenderBackend *self)
|
||||
SDL_Log("Initializing screen (%sx%s = %dx%d)", width->data, height->data, screenwidth, screenheight);
|
||||
PASS(e, akgl_heap_release_string(width));
|
||||
PASS(e, akgl_heap_release_string(height));
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(
|
||||
e,
|
||||
SDL_CreateWindowAndRenderer(game.uri, screenwidth, screenheight, 0, &window, &self->sdl_renderer),
|
||||
AKGL_ERR_SDL,
|
||||
"Couldn't create window/renderer: %s",
|
||||
SDL_GetError());
|
||||
|
||||
|
||||
camera->x = 0;
|
||||
camera->y = 0;
|
||||
camera->w = screenwidth;
|
||||
@@ -133,9 +133,9 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_render_2d_draw_world(akgl_RenderBackend
|
||||
if ( actor->layer != i ) {
|
||||
continue;
|
||||
}
|
||||
PASS(e, actor->renderfunc(actor));
|
||||
PASS(e, actor->renderfunc(actor));
|
||||
}
|
||||
}
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
20
src/sprite.c
20
src/sprite.c
@@ -85,10 +85,10 @@ static akerr_ErrorContext *akgl_sprite_load_json_spritesheet(json_t *json, akgl_
|
||||
IGNORE(akgl_heap_release_string(tmpstr));
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
akerr_ErrorContext *akgl_sprite_load_json(char *filename)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -100,7 +100,7 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename)
|
||||
akgl_String *spritename = NULL;
|
||||
akgl_String *filename_copy = NULL;
|
||||
int i = 0;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, filename, AKERR_NULLPOINTER, "Received null filename");
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_sprite(&obj));
|
||||
@@ -125,7 +125,7 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename)
|
||||
AKERR_NULLPOINTER,
|
||||
"Error while loading sprite from %s on line %d: %s", filename, error.line, error.text
|
||||
);
|
||||
|
||||
|
||||
CATCH(errctx, akgl_sprite_load_json_spritesheet((json_t *)json, &sheet, dirname(filename_copy->data)));
|
||||
CATCH(errctx, akgl_get_json_string_value((json_t *)json, "name", &spritename));
|
||||
CATCH(errctx,
|
||||
@@ -134,14 +134,14 @@ akerr_ErrorContext *akgl_sprite_load_json(char *filename)
|
||||
spritename->data,
|
||||
(akgl_SpriteSheet *)sheet)
|
||||
);
|
||||
|
||||
|
||||
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "width", &obj->width));
|
||||
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "height", &obj->height));
|
||||
CATCH(errctx, akgl_get_json_integer_value((json_t *)json, "speed", &obj->speed));
|
||||
obj->speed = obj->speed * AKGL_TIME_ONESEC_MS;
|
||||
CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loop", &obj->loop));
|
||||
CATCH(errctx, akgl_get_json_boolean_value((json_t *)json, "loopReverse", &obj->loopReverse));
|
||||
|
||||
|
||||
CATCH(errctx, akgl_get_json_array_value((json_t *)json, "frames", &frames));
|
||||
obj->frames = json_array_size((json_t *)frames);
|
||||
for ( i = 0 ; i < obj->frames; i++ ) {
|
||||
@@ -166,7 +166,7 @@ akerr_ErrorContext *akgl_sprite_initialize(akgl_Sprite *spr, char *name, akgl_Sp
|
||||
FAIL_ZERO_RETURN(errctx, spr, AKERR_NULLPOINTER, "Null sprite reference");
|
||||
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Empty sprite name");
|
||||
FAIL_ZERO_RETURN(errctx, sheet, AKERR_NULLPOINTER, "Null spritesheet reference");
|
||||
|
||||
|
||||
memset(spr, 0x00, sizeof(akgl_Sprite));
|
||||
memcpy(spr->name, name, AKGL_SPRITE_MAX_NAME_LENGTH);
|
||||
spr->sheet = sheet;
|
||||
@@ -191,14 +191,14 @@ akerr_ErrorContext *akgl_spritesheet_initialize(akgl_SpriteSheet *sheet, int spr
|
||||
memset(sheet, 0x00, sizeof(akgl_SpriteSheet));
|
||||
|
||||
//CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
|
||||
//CATCH(errctx, akgl_string_initialize(tmpstr, NULL));
|
||||
strncpy((char *)&sheet->name, filename, AKGL_SPRITE_SHEET_MAX_FILENAME_LENGTH);
|
||||
|
||||
|
||||
//snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), filename);
|
||||
sheet->texture = IMG_LoadTexture(renderer->sdl_renderer, filename);
|
||||
FAIL_ZERO_BREAK(errctx, sheet->texture, AKGL_ERR_SDL, "Failed loading asset %s : %s", filename, SDL_GetError());
|
||||
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_SetPointerProperty(AKGL_REGISTRY_SPRITESHEET, (char *)sheet->name, (void *)sheet),
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
|
||||
akerr_ErrorContext *akgl_string_initialize(akgl_String *obj, char *init)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "Attempted to initialize NULL string reference");
|
||||
if ( init != NULL ) {
|
||||
strncpy((char *)&obj->data, init, AKGL_MAX_STRING_LENGTH);
|
||||
} else {
|
||||
memset(&obj->data, 0x00, sizeof(akgl_String));
|
||||
}
|
||||
obj->refcount = 1;
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, obj, AKERR_NULLPOINTER, "Attempted to initialize NULL string reference");
|
||||
if ( init != NULL ) {
|
||||
strncpy((char *)&obj->data, init, AKGL_MAX_STRING_LENGTH);
|
||||
} else {
|
||||
memset(&obj->data, 0x00, sizeof(akgl_String));
|
||||
}
|
||||
obj->refcount = 1;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_string_copy(akgl_String *src, akgl_String *dst, int count)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akgl_text_loadfont(char *name, char *filepath, int size)
|
||||
{
|
||||
TTF_Font *font = NULL;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null font name");
|
||||
FAIL_ZERO_RETURN(errctx, name, AKERR_NULLPOINTER, "Null filepath");
|
||||
@@ -35,7 +35,7 @@ akerr_ErrorContext AKERR_NOIGNORE *akgl_text_rendertextat(TTF_Font *font, char *
|
||||
SDL_Surface *textsurf = NULL;
|
||||
SDL_Texture *texture = NULL;
|
||||
SDL_FRect dest;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, font, AKERR_NULLPOINTER, "NULL font");
|
||||
FAIL_ZERO_RETURN(errctx, text, AKERR_NULLPOINTER, "NULL text string");
|
||||
|
||||
@@ -60,7 +60,7 @@ akerr_ErrorContext *akgl_get_json_tilemap_property(json_t *obj, char *key, char
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
FAIL_RETURN(errctx, AKERR_KEY, "Property not found in properties map");
|
||||
}
|
||||
|
||||
@@ -68,12 +68,12 @@ akerr_ErrorContext *akgl_get_json_properties_string(json_t *obj, char *key, akgl
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
json_t *property;
|
||||
|
||||
|
||||
PASS(errctx, akgl_get_json_tilemap_property(obj, key, "string", &property));
|
||||
PASS(errctx, akgl_heap_next_string(dest));
|
||||
PASS(errctx, akgl_string_initialize(*dest, NULL));
|
||||
PASS(errctx, akgl_get_json_string_value(property, "value", dest));
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ akerr_ErrorContext *akgl_get_json_properties_integer(json_t *obj, char *key, int
|
||||
json_t *property = NULL;
|
||||
PASS(errctx, akgl_get_json_tilemap_property(obj, key, "int", &property));
|
||||
PASS(errctx, akgl_get_json_integer_value(property, "value", dest));
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ akerr_ErrorContext *akgl_get_json_properties_number(json_t *obj, char *key, floa
|
||||
json_t *property = NULL;
|
||||
PASS(errctx, akgl_get_json_tilemap_property(obj, key, "number", &property));
|
||||
PASS(errctx, akgl_get_json_number_value(property, "value", dest));
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ akerr_ErrorContext *akgl_get_json_properties_float(json_t *obj, char *key, float
|
||||
json_t *property = NULL;
|
||||
PASS(errctx, akgl_get_json_tilemap_property(obj, key, "float", &property));
|
||||
PASS(errctx, akgl_get_json_number_value(property, "value", dest));
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ akerr_ErrorContext *akgl_get_json_properties_double(json_t *obj, char *key, doub
|
||||
json_t *property = NULL;
|
||||
PASS(errctx, akgl_get_json_tilemap_property(obj, key, "float", &property));
|
||||
PASS(errctx, akgl_get_json_double_value(property, "value", dest));
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilema
|
||||
PREPARE_ERROR(e);
|
||||
akgl_String *tmpstr = NULL;
|
||||
akgl_String *tmppath = NULL;
|
||||
|
||||
|
||||
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "columns", &dest->tilesets[tsidx].columns));
|
||||
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "firstgid", &dest->tilesets[tsidx].firstgid));
|
||||
PASS(e, akgl_get_json_integer_value((json_t *)tileset, "imageheight", &dest->tilesets[tsidx].imageheight));
|
||||
@@ -164,8 +164,8 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilema
|
||||
(char *)&tmpstr->data,
|
||||
AKGL_TILEMAP_MAX_TILESET_NAME_SIZE
|
||||
);
|
||||
|
||||
CATCH(e, akgl_get_json_string_value((json_t *)tileset, "image", &tmpstr));
|
||||
|
||||
CATCH(e, akgl_get_json_string_value((json_t *)tileset, "image", &tmpstr));
|
||||
CATCH(e, akgl_path_relative((char *)&dirname->data, (char *)&tmpstr->data, tmppath));
|
||||
strncpy((char *)&dest->tilesets[tsidx].imagefilename, tmppath->data, AKGL_MAX_STRING_LENGTH);
|
||||
} CLEANUP {
|
||||
@@ -173,10 +173,10 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilema
|
||||
IGNORE(akgl_heap_release_string(tmppath));
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
|
||||
|
||||
dest->tilesets[tsidx].texture = IMG_LoadTexture(renderer->sdl_renderer, (char *)&dest->tilesets[tsidx].imagefilename);
|
||||
FAIL_ZERO_RETURN(e, dest->tilesets[tsidx].texture, AKERR_NULLPOINTER, "Failed loading tileset image : %s", SDL_GetError());
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ akerr_ErrorContext *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int
|
||||
for (j = 0; j <= (dest->tilesets[tilesetidx].tilecount); j++) {
|
||||
/*
|
||||
* For a given 8x2 tilemap like this with 10x10 tiles and 0 spacing and 0 margin
|
||||
*
|
||||
*
|
||||
* 01234567
|
||||
* 89ABCDEF
|
||||
*
|
||||
@@ -221,7 +221,7 @@ akerr_ErrorContext *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int
|
||||
x_offset = (j * (dest->tilesets[tilesetidx].tilewidth + dest->tilesets[tilesetidx].spacing));
|
||||
y_offset = dest->tilesets[tilesetidx].spacing;
|
||||
}
|
||||
|
||||
|
||||
dest->tilesets[tilesetidx].tile_offsets[j][0] = x_offset;
|
||||
dest->tilesets[tilesetidx].tile_offsets[j][1] = y_offset;
|
||||
/* SDL_Log("Tileset %s index (%d, %d) is offset (%d, %d)",
|
||||
@@ -233,8 +233,8 @@ akerr_ErrorContext *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int
|
||||
// SDL_Log("Processed %d total tiles for tileset", j);
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root, akgl_String *dirname)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -244,7 +244,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root,
|
||||
json_t *tilesets = NULL;
|
||||
json_t *jstileset = NULL;
|
||||
int i;
|
||||
|
||||
|
||||
dest->numtilesets = 0;
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_get_json_array_value(root, "tilesets", &tilesets))
|
||||
@@ -257,7 +257,7 @@ akerr_ErrorContext *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root,
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *cur
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_String *tmpstr = NULL;
|
||||
akgl_Actor *actorobj = NULL;
|
||||
|
||||
|
||||
curobj->type = AKGL_TILEMAP_OBJECT_TYPE_ACTOR;
|
||||
if ( strlen((char *)&curobj->name) == 0 ) {
|
||||
FAIL_RETURN(errctx, AKERR_KEY, "Actor in tile object layer cannot have empty name");
|
||||
@@ -304,7 +304,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_object_actor(akgl_TilemapObject *cur
|
||||
}
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
actorobj->layer = layerid;
|
||||
actorobj->x = curobj->x;
|
||||
actorobj->y = curobj->y;
|
||||
@@ -340,7 +340,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *
|
||||
PASS(errctx, akgl_get_json_number_value((json_t *)layerdatavalue, "x", &curobj->x));
|
||||
PASS(errctx, akgl_get_json_number_value((json_t *)layerdatavalue, "y", &curobj->y));
|
||||
PASS(errctx, akgl_get_json_boolean_value((json_t *)layerdatavalue, "visible", &curobj->visible));
|
||||
|
||||
|
||||
PASS(errctx, akgl_get_json_string_value((json_t *)layerdatavalue, "type", &tmpstr));
|
||||
if ( strcmp(tmpstr->data, "actor") == 0 ) {
|
||||
PASS(errctx, akgl_tilemap_load_layer_object_actor(curobj, layerdatavalue, layerid, dirname));
|
||||
@@ -354,11 +354,11 @@ akerr_ErrorContext *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *
|
||||
dest->p_vanishing_y = curobj->y;
|
||||
PASS(errctx, akgl_get_json_integer_value((json_t *)layerdatavalue, "height", &dest->p_vanishing_h));
|
||||
PASS(errctx, akgl_get_json_properties_float((json_t *)layerdatavalue, "scale", &dest->p_vanishing_scale));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
layerdatavalue = NULL;
|
||||
}
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *roo
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -417,15 +417,15 @@ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *ro
|
||||
CATCH(errctx, akgl_get_json_string_value(root, "image", &tmpstr));
|
||||
|
||||
DISABLE_GCC_WARNING_FORMAT_TRUNCATION
|
||||
snprintf((char *)&fpath->data,
|
||||
AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE,
|
||||
"%s/%s",
|
||||
dirname->data,
|
||||
tmpstr->data
|
||||
);
|
||||
snprintf((char *)&fpath->data,
|
||||
AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE,
|
||||
"%s/%s",
|
||||
dirname->data,
|
||||
tmpstr->data
|
||||
);
|
||||
RESTORE_GCC_WARNINGS
|
||||
|
||||
dest->layers[layerid].texture = IMG_LoadTexture(renderer->sdl_renderer, (char *)fpath->data);
|
||||
|
||||
dest->layers[layerid].texture = IMG_LoadTexture(renderer->sdl_renderer, (char *)fpath->data);
|
||||
FAIL_ZERO_BREAK(errctx, dest->layers[layerid].texture, AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
dest->layers[layerid].width = dest->layers[layerid].texture->w;
|
||||
dest->layers[layerid].height = dest->layers[layerid].texture->h;
|
||||
@@ -434,7 +434,7 @@ akerr_ErrorContext *akgl_tilemap_load_layer_image(akgl_Tilemap *dest, json_t *ro
|
||||
IGNORE(akgl_heap_release_string(fpath));
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ akerr_ErrorContext *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, a
|
||||
int i;
|
||||
int layerid = 0;
|
||||
int tmpint = 0;
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_get_json_array_value(root, "layers", &layers));
|
||||
dest->numlayers = json_array_size((json_t *)layers);
|
||||
@@ -500,7 +500,7 @@ akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root)
|
||||
json_t *props = NULL;
|
||||
akgl_String *tmpval = NULL;
|
||||
double defzero = 0.0;
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(e, akgl_heap_next_string(&tmpval));
|
||||
CATCH(e, akgl_get_json_array_value((json_t *)root, "properties", &props));
|
||||
@@ -523,7 +523,7 @@ akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root)
|
||||
);
|
||||
PASS(e, akgl_physics_factory(&dest->physics, tmpval));
|
||||
dest->use_own_physics = true;
|
||||
|
||||
|
||||
CATCH(e, akgl_get_json_with_default(
|
||||
akgl_get_json_properties_double(
|
||||
root, "physics.gravity.x", &dest->physics.gravity_x
|
||||
@@ -572,7 +572,7 @@ akerr_ErrorContext *akgl_tilemap_load_physics(akgl_Tilemap *dest, json_t *root)
|
||||
} HANDLE(e, AKERR_KEY) {
|
||||
SDL_Log("Map uses game physics");
|
||||
} FINISH(e, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
@@ -583,7 +583,7 @@ akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest)
|
||||
//akgl_String *tmpstr = NULL;
|
||||
json_error_t error;
|
||||
akgl_String *dirnamestr = NULL;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, fname, AKERR_NULLPOINTER, "load_tilemap received null filename");
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "load_tilemap received null tilemap");
|
||||
|
||||
@@ -598,7 +598,7 @@ akerr_ErrorContext *akgl_tilemap_load(char *fname, akgl_Tilemap *dest)
|
||||
//SDL_snprintf(tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), fname);
|
||||
CATCH(errctx, aksl_realpath(fname, (char *)&dirnamestr->data));
|
||||
dirname((char *)&dirnamestr->data);
|
||||
|
||||
|
||||
json = json_load_file(fname, 0, &error);
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
@@ -696,13 +696,13 @@ akerr_ErrorContext *akgl_tilemap_draw(akgl_Tilemap *map, SDL_FRect *viewport, in
|
||||
dest.x = 0;
|
||||
dest.y = 0;
|
||||
src.w = map->layers[layeridx].width;
|
||||
src.h = map->layers[layeridx].height;
|
||||
src.h = map->layers[layeridx].height;
|
||||
dest.w = map->layers[layeridx].width;
|
||||
dest.h = map->layers[layeridx].height;
|
||||
PASS(errctx, renderer->draw_texture(renderer, map->layers[layeridx].texture, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
dest.x = 0;
|
||||
dest.y = 0;
|
||||
dest.w = map->tilewidth;
|
||||
@@ -778,7 +778,7 @@ akerr_ErrorContext *akgl_tilemap_draw_tileset(akgl_Tilemap *map, int tilesetidx)
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, map, AKERR_NULLPOINTER, "akgl_tilemap_draw_tileset received NULL pointer to tilemap");
|
||||
FAIL_NONZERO_RETURN(errctx, (tilesetidx >= map->numtilesets), AKERR_OUTOFBOUNDS, "akgl_tilemap_draw_tileset received a tileset index out of bounds");
|
||||
|
||||
|
||||
for ( tilenum = 0; tilenum < map->tilesets[tilesetidx].tilecount; tilenum++) {
|
||||
// Render this tile to the correct screen position
|
||||
// FIXME: These conditionals are probably not very efficient. Need a better way of getting
|
||||
|
||||
178
src/util.c
178
src/util.c
@@ -38,14 +38,14 @@ akerr_ErrorContext *akgl_path_relative_root(char *root, char *path, akgl_String
|
||||
int rootlen;
|
||||
int pathlen;
|
||||
int count;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "NULL argument");
|
||||
FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "NULL argument");
|
||||
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument");
|
||||
|
||||
PASS(e, akgl_heap_next_string(&strbuf));
|
||||
PASS(e, akgl_heap_next_string(&pathbuf));
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
// Is it relative to the root?
|
||||
rootlen = strlen(root);
|
||||
@@ -54,10 +54,10 @@ akerr_ErrorContext *akgl_path_relative_root(char *root, char *path, akgl_String
|
||||
FAIL_RETURN(e, AKERR_OUTOFBOUNDS, "Total path length (%d) is greater than maximum akgl_String length (%d)", (rootlen + pathlen), AKGL_MAX_STRING_LENGTH);
|
||||
}
|
||||
DISABLE_GCC_WARNING_FORMAT_TRUNCATION
|
||||
CATCH(e, aksl_sprintf(&count, (char *)&pathbuf->data, "%s/%s", root, path));
|
||||
CATCH(e, aksl_sprintf(&count, (char *)&pathbuf->data, "%s/%s", root, path));
|
||||
RESTORE_GCC_WARNINGS
|
||||
CATCH(e, aksl_realpath((char *)&pathbuf->data, (char *)&strbuf->data));
|
||||
CATCH(e, akgl_string_copy(strbuf, dst, 0));
|
||||
CATCH(e, aksl_realpath((char *)&pathbuf->data, (char *)&strbuf->data));
|
||||
CATCH(e, akgl_string_copy(strbuf, dst, 0));
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_string(strbuf));
|
||||
IGNORE(akgl_heap_release_string(pathbuf));
|
||||
@@ -71,18 +71,18 @@ akerr_ErrorContext *akgl_path_relative(char *root, char *path, akgl_String *dst)
|
||||
PREPARE_ERROR(e);
|
||||
akgl_String *strbuf;
|
||||
char *result;
|
||||
|
||||
|
||||
FAIL_ZERO_RETURN(e, root, AKERR_NULLPOINTER, "NULL argument");
|
||||
FAIL_ZERO_RETURN(e, path, AKERR_NULLPOINTER, "NULL argument");
|
||||
FAIL_ZERO_RETURN(e, dst, AKERR_NULLPOINTER, "NULL argument");
|
||||
|
||||
PASS(e, akgl_heap_next_string(&strbuf));
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
// Is path relative to our current working directory?
|
||||
CATCH(e, aksl_realpath(path, (char *)&strbuf->data));
|
||||
// Yes it is. strbuf->data contains the absolute path.
|
||||
CATCH(e, akgl_string_copy(strbuf, dst, 0));
|
||||
CATCH(e, akgl_string_copy(strbuf, dst, 0));
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_string(strbuf));
|
||||
} PROCESS(e) {
|
||||
@@ -111,102 +111,102 @@ akerr_ErrorContext *akgl_path_relative_from(char *path, char *from, akgl_String
|
||||
PASS(e, akgl_heap_next_string(&dirnamestr));
|
||||
PASS(e, aksl_realpath(from, (char *)&dirnamestr->data));
|
||||
dirname((char *)&dirnamestr->data);
|
||||
|
||||
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_rectangle_points(RectanglePoints *dest, SDL_FRect *rect)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL RectanglePoints reference");
|
||||
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "NULL Rectangle reference");
|
||||
dest->topleft.x = rect->x;
|
||||
dest->topleft.y = rect->y;
|
||||
dest->bottomleft.x = rect->x;
|
||||
dest->bottomleft.y = rect->y + rect->h;
|
||||
dest->topright.x = rect->x + rect->w;
|
||||
dest->topright.y = rect->y;
|
||||
dest->bottomright.x = rect->x + rect->w;
|
||||
dest->bottomright.y = rect->y + rect->h;
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, dest, AKERR_NULLPOINTER, "NULL RectanglePoints reference");
|
||||
FAIL_ZERO_RETURN(errctx, rect, AKERR_NULLPOINTER, "NULL Rectangle reference");
|
||||
dest->topleft.x = rect->x;
|
||||
dest->topleft.y = rect->y;
|
||||
dest->bottomleft.x = rect->x;
|
||||
dest->bottomleft.y = rect->y + rect->h;
|
||||
dest->topright.x = rect->x + rect->w;
|
||||
dest->topright.y = rect->y;
|
||||
dest->bottomright.x = rect->x + rect->w;
|
||||
dest->bottomright.y = rect->y + rect->h;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_collide_point_rectangle(point *p, RectanglePoints *rp, bool *collide)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, p, AKERR_NULLPOINTER, "NULL Point reference");
|
||||
FAIL_ZERO_RETURN(errctx, rp, AKERR_NULLPOINTER, "NULL RectanglePoints reference");
|
||||
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL boolean reference");
|
||||
if ( (p->x >= rp->topleft.x) && (p->y >= rp->topleft.y) &&
|
||||
(p->x <= rp->bottomright.x) && (p->y <= rp->bottomright.y) ) {
|
||||
*collide = true;
|
||||
} else {
|
||||
*collide = false;
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, p, AKERR_NULLPOINTER, "NULL Point reference");
|
||||
FAIL_ZERO_RETURN(errctx, rp, AKERR_NULLPOINTER, "NULL RectanglePoints reference");
|
||||
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL boolean reference");
|
||||
if ( (p->x >= rp->topleft.x) && (p->y >= rp->topleft.y) &&
|
||||
(p->x <= rp->bottomright.x) && (p->y <= rp->bottomright.y) ) {
|
||||
*collide = true;
|
||||
} else {
|
||||
*collide = false;
|
||||
}
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_collide_rectangles(SDL_FRect *r1, SDL_FRect *r2, bool *collide)
|
||||
{
|
||||
RectanglePoints r1p;
|
||||
RectanglePoints r2p;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference");
|
||||
FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference");
|
||||
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL collision flag reference");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&r1p, r1));
|
||||
CATCH(errctx, akgl_rectangle_points(&r2p, r2));
|
||||
|
||||
// is the upper left corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topleft, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the upper left corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topleft, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the top right corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topright, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the top right corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topright, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom left corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomleft, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom left corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomleft, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom right corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomright, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom right corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomright, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
RectanglePoints r1p;
|
||||
RectanglePoints r2p;
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, r1, AKERR_NULLPOINTER, "NULL rectangle reference");
|
||||
FAIL_ZERO_RETURN(errctx, r2, AKERR_NULLPOINTER, "NULL rectangle reference");
|
||||
FAIL_ZERO_RETURN(errctx, collide, AKERR_NULLPOINTER, "NULL collision flag reference");
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
*collide = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&r1p, r1));
|
||||
CATCH(errctx, akgl_rectangle_points(&r2p, r2));
|
||||
|
||||
// is the upper left corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topleft, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the upper left corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topleft, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the top right corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.topright, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the top right corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.topright, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom left corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomleft, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom left corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomleft, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom right corner of r1 contacting r2?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r1p.bottomright, &r2p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
// is the bottom right corner of r2 contacting r1?
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&r2p.bottomright, &r1p, collide));
|
||||
if ( *collide == true ) { SUCCEED_RETURN(errctx); }
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
*collide = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
|
||||
akerr_ErrorContext *akgl_compare_sdl_surfaces(SDL_Surface *s1, SDL_Surface *s2)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, s1, AKERR_NULLPOINTER, "NULL Surface pointer");
|
||||
FAIL_ZERO_RETURN(errctx, s2, AKERR_NULLPOINTER, "NULL Surface pointer");
|
||||
FAIL_NONZERO_RETURN(errctx, memcmp(s1->pixels, s2->pixels, (s1->pitch * s1->h)), AKERR_VALUE, "Comparison surfaces are not equal");
|
||||
SUCCEED_RETURN(errctx);
|
||||
PREPARE_ERROR(errctx);
|
||||
FAIL_ZERO_RETURN(errctx, s1, AKERR_NULLPOINTER, "NULL Surface pointer");
|
||||
FAIL_ZERO_RETURN(errctx, s2, AKERR_NULLPOINTER, "NULL Surface pointer");
|
||||
FAIL_NONZERO_RETURN(errctx, memcmp(s1->pixels, s2->pixels, (s1->pitch * s1->h)), AKERR_VALUE, "Comparison surfaces are not equal");
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, int x, int y, int w, int h, char *writeout)
|
||||
@@ -228,7 +228,7 @@ akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, in
|
||||
CATCH(errctx, renderer->draw_texture(renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
||||
s1 = SDL_RenderReadPixels(renderer->sdl_renderer, &read);
|
||||
FAIL_ZERO_BREAK(errctx, s1, AKGL_ERR_SDL, "Failed to read pixels from renderer");
|
||||
|
||||
|
||||
if ( writeout != NULL ) {
|
||||
snprintf((char *)&tmpstring->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), writeout);
|
||||
FAIL_ZERO_BREAK(
|
||||
@@ -239,13 +239,13 @@ akerr_ErrorContext *akgl_render_and_compare(SDL_Texture *t1, SDL_Texture *t2, in
|
||||
(char *)&tmpstring->data,
|
||||
SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
SDL_RenderClear(renderer->sdl_renderer);
|
||||
|
||||
|
||||
CATCH(errctx, renderer->draw_texture(renderer, t1, &src, &dest, 0, NULL, SDL_FLIP_NONE));
|
||||
s2 = SDL_RenderReadPixels(renderer->sdl_renderer, &read);
|
||||
FAIL_ZERO_BREAK(errctx, s2, AKGL_ERR_SDL, "Failed to read pixels from renderer");
|
||||
|
||||
|
||||
CATCH(errctx, akgl_compare_sdl_surfaces(s1, s2));
|
||||
} CLEANUP {
|
||||
if ( s1 != NULL )
|
||||
|
||||
15
src/version.c
Normal file
15
src/version.c
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @file version.c
|
||||
* @brief Implements the runtime half of the version API.
|
||||
*/
|
||||
|
||||
#include <akgl/version.h>
|
||||
|
||||
const char *akgl_version(void)
|
||||
{
|
||||
// AKGL_VERSION is baked in when this translation unit is compiled, so what
|
||||
// comes back is the version of the shared library the caller linked -- not
|
||||
// the version of the header the caller built against. That asymmetry is the
|
||||
// whole point: comparing the two detects a stale libakgl on the loader path.
|
||||
return AKGL_VERSION;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#define UNHANDLED_ERROR_TERMINATION_BEHAVIOR \
|
||||
#define UNHANDLED_ERROR_TERMINATION_BEHAVIOR \
|
||||
handle_unhandled_error(errctx);
|
||||
|
||||
#include <akerror.h>
|
||||
@@ -58,7 +58,7 @@ akerr_ErrorContext *akgl_actor_render_noop(akgl_Actor *obj)
|
||||
akerr_ErrorContext *test_registry_actor_iterator_nullpointers(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
akerr_ErrorUnhandledErrorHandler defaulthandler = akerr_handler_unhandled_error;
|
||||
|
||||
akerr_handler_unhandled_error = handle_unhandled_error_noexit;
|
||||
@@ -151,7 +151,7 @@ akerr_ErrorContext *test_registry_actor_iterator_updaterender(void)
|
||||
} FINISH(unhandled_error_context, true);
|
||||
|
||||
akerr_handler_unhandled_error = defaulthandler;
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ akerr_ErrorContext *test_akgl_actor_set_character(void)
|
||||
{
|
||||
akgl_Actor *testactor = NULL;
|
||||
akgl_Character *testchar = NULL;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -167,10 +167,10 @@ akerr_ErrorContext *test_akgl_actor_set_character(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
printf("Handled\n");
|
||||
printf("Handled\n");
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_actor(&testactor));
|
||||
|
||||
@@ -178,13 +178,13 @@ akerr_ErrorContext *test_akgl_actor_set_character(void)
|
||||
testactor->layer = 0;
|
||||
testactor->updatefunc = &akgl_actor_update_noop;
|
||||
testactor->renderfunc = &akgl_actor_render_noop;
|
||||
|
||||
|
||||
CATCH(errctx, akgl_actor_set_character(testactor, "test"));
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_actor(testactor));
|
||||
IGNORE(akgl_heap_release_actor(testactor));
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
printf("Handled\n");
|
||||
printf("Handled\n");
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -195,13 +195,13 @@ akerr_ErrorContext *test_akgl_actor_set_character(void)
|
||||
testactor->layer = 0;
|
||||
testactor->updatefunc = &akgl_actor_update_noop;
|
||||
testactor->renderfunc = &akgl_actor_render_noop;
|
||||
|
||||
|
||||
CATCH(errctx, akgl_character_initialize(testchar, "test"));
|
||||
|
||||
|
||||
CATCH(errctx, akgl_actor_set_character(testactor, "test"));
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_actor(testactor));
|
||||
IGNORE(akgl_heap_release_character(testchar));
|
||||
IGNORE(akgl_heap_release_actor(testactor));
|
||||
IGNORE(akgl_heap_release_character(testchar));
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
@@ -248,7 +248,7 @@ akerr_ErrorContext *test_actor_manage_children(void)
|
||||
// Expected behavior
|
||||
SDL_Log("addchild throws AKERR_RELATIONSHIP when child already has a parent");
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_actor(&child));
|
||||
CATCH(errctx, parent->addchild(parent, child));
|
||||
@@ -284,7 +284,7 @@ akerr_ErrorContext *test_actor_manage_children(void)
|
||||
"Child %s was not removed from the registry",
|
||||
(char *)&tmpstring->data);
|
||||
}
|
||||
_test_actor_addchild_heaprelease_cleanup:
|
||||
_test_actor_addchild_heaprelease_cleanup:
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -309,7 +309,7 @@ _test_actor_addchild_heaprelease_cleanup:
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
@@ -903,6 +903,7 @@ int main(void)
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, akgl_registry_init_actor());
|
||||
CATCH(errctx, akgl_registry_init_sprite());
|
||||
CATCH(errctx, akgl_registry_init_spritesheet());
|
||||
|
||||
@@ -3,29 +3,29 @@
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int mask = 0;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_ALIVE);
|
||||
if ( mask != AKGL_ACTOR_STATE_ALIVE )
|
||||
return 1;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
if ( mask != (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT) )
|
||||
return 1;
|
||||
AKGL_BITMASK_DEL(mask, AKGL_ACTOR_STATE_ALIVE);
|
||||
if ( mask != (AKGL_ACTOR_STATE_FACE_LEFT) )
|
||||
return 1;
|
||||
AKGL_BITMASK_CLEAR(mask);
|
||||
if ( mask != 0 )
|
||||
return 1;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
if ( !(AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_FACE_LEFT)) )
|
||||
return 1;
|
||||
mask = AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP;
|
||||
AKGL_BITMASK_DEL(mask, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
if ( mask != AKGL_ACTOR_STATE_ALIVE )
|
||||
return 1;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_MOVING_DOWN);
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_DOWN);
|
||||
if ( mask != (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_MOVING_DOWN | AKGL_ACTOR_STATE_FACE_DOWN) )
|
||||
return 1;
|
||||
return 0;
|
||||
int mask = 0;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_ALIVE);
|
||||
if ( mask != AKGL_ACTOR_STATE_ALIVE )
|
||||
return 1;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
if ( mask != (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_LEFT) )
|
||||
return 1;
|
||||
AKGL_BITMASK_DEL(mask, AKGL_ACTOR_STATE_ALIVE);
|
||||
if ( mask != (AKGL_ACTOR_STATE_FACE_LEFT) )
|
||||
return 1;
|
||||
AKGL_BITMASK_CLEAR(mask);
|
||||
if ( mask != 0 )
|
||||
return 1;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_LEFT);
|
||||
if ( !(AKGL_BITMASK_HAS(mask, AKGL_ACTOR_STATE_FACE_LEFT)) )
|
||||
return 1;
|
||||
mask = AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_FACE_UP;
|
||||
AKGL_BITMASK_DEL(mask, AKGL_ACTOR_STATE_FACE_ALL);
|
||||
if ( mask != AKGL_ACTOR_STATE_ALIVE )
|
||||
return 1;
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_MOVING_DOWN);
|
||||
AKGL_BITMASK_ADD(mask, AKGL_ACTOR_STATE_FACE_DOWN);
|
||||
if ( mask != (AKGL_ACTOR_STATE_ALIVE | AKGL_ACTOR_STATE_MOVING_DOWN | AKGL_ACTOR_STATE_FACE_DOWN) )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ akerr_ErrorContext *test_character_iterate_state_sprites()
|
||||
akgl_Sprite *testsprite = NULL;
|
||||
akgl_Sprite *testsprite2 = NULL;
|
||||
akgl_Iterator opflags = {.flags = AKGL_ITERATOR_OP_RELEASE, .layerid = 0};
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_character(&testchar));
|
||||
@@ -126,7 +126,7 @@ akerr_ErrorContext *test_character_iterate_state_sprites()
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_sprite(testsprite));
|
||||
IGNORE(akgl_heap_release_sprite(testsprite2));
|
||||
IGNORE(akgl_heap_release_character(testchar));
|
||||
IGNORE(akgl_heap_release_character(testchar));
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
@@ -140,7 +140,7 @@ akerr_ErrorContext *test_akgl_character_load_json()
|
||||
akgl_Sprite *comparesprite = NULL;
|
||||
int tsrc = 0;
|
||||
int tsrc2 = 0;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_character(&testcharacter));
|
||||
@@ -158,7 +158,7 @@ akerr_ErrorContext *test_akgl_character_load_json()
|
||||
testsprite2,
|
||||
AKERR_KEY,
|
||||
"Sprite 2 loaded from json but not in registry");
|
||||
|
||||
|
||||
CATCH(errctx, akgl_character_load_json("assets/testcharacter.json"));
|
||||
testcharacter = SDL_GetPointerProperty(AKGL_REGISTRY_CHARACTER, "testcharacter", NULL);
|
||||
FAIL_ZERO_BREAK(
|
||||
@@ -195,12 +195,13 @@ int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_character", 640, 480, SDL_WINDOW_HIDDEN, &window, &renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
|
||||
int numsprites = 8;
|
||||
char *spritepaths[] = {
|
||||
"assets/sprites/little_guy_walking_left.json",
|
||||
"assets/sprites/little_guy_walking_right.json",
|
||||
"assets/sprites/little_guy_walking_up.json",
|
||||
"assets/sprites/little_guy_walking_down.json",
|
||||
"assets/sprites/little_guy_facing_left.json",
|
||||
"assets/sprites/little_guy_facing_right.json",
|
||||
"assets/sprites/little_guy_facing_up.json",
|
||||
"assets/sprites/little_guy_facing_down.json"
|
||||
"assets/sprites/little_guy_walking_left.json",
|
||||
"assets/sprites/little_guy_walking_right.json",
|
||||
"assets/sprites/little_guy_walking_up.json",
|
||||
"assets/sprites/little_guy_walking_down.json",
|
||||
"assets/sprites/little_guy_facing_left.json",
|
||||
"assets/sprites/little_guy_facing_right.json",
|
||||
"assets/sprites/little_guy_facing_up.json",
|
||||
"assets/sprites/little_guy_facing_down.json"
|
||||
};
|
||||
|
||||
int main(void)
|
||||
@@ -34,15 +34,15 @@ int main(void)
|
||||
PREPARE_ERROR(errctx);
|
||||
SDL3GControlMap *controlmap;
|
||||
actor *actorptr = NULL;
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
|
||||
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 640, 480, 0, &window, &renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
@@ -55,7 +55,7 @@ int main(void)
|
||||
strcpy((char *)&game.uri, "net.aklabs.libakgl.charviewer");
|
||||
game.screenwidth = 640;
|
||||
game.screenheight = 480;
|
||||
|
||||
|
||||
CATCH(errctx, akgl_GAME_init());
|
||||
|
||||
for ( int i = 0; i < numsprites ; i++) {
|
||||
@@ -96,7 +96,7 @@ int main(void)
|
||||
controlmap->controls[1].target_del_state_off = AKGL_ACTOR_STATE_MOVING_UP;
|
||||
controlmap->controls[1].event_on = SDL_EVENT_KEY_DOWN;
|
||||
controlmap->controls[1].event_off = SDL_EVENT_KEY_UP;
|
||||
|
||||
|
||||
// Move left
|
||||
controlmap->controls[2].key = SDLK_LEFT;
|
||||
//controlmap->controls[2].target_state_gate = AKGL_ACTOR_STATE_MOVING_LEFT;
|
||||
@@ -105,13 +105,13 @@ int main(void)
|
||||
controlmap->controls[2].target_del_state_off = AKGL_ACTOR_STATE_MOVING_LEFT;
|
||||
controlmap->controls[2].event_on = SDL_EVENT_KEY_DOWN;
|
||||
controlmap->controls[2].event_off = SDL_EVENT_KEY_UP;
|
||||
|
||||
|
||||
// Move right
|
||||
controlmap->controls[3].key = SDLK_RIGHT;
|
||||
//controlmap->controls[3].target_state_gate = AKGL_ACTOR_STATE_MOVING_RIGHT;
|
||||
controlmap->controls[3].target_add_state_on = AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_FACE_RIGHT;
|
||||
controlmap->controls[3].target_del_state_on = AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_FACE_ALL;
|
||||
controlmap->controls[3].target_del_state_off = AKGL_ACTOR_STATE_MOVING_RIGHT;
|
||||
controlmap->controls[3].target_del_state_off = AKGL_ACTOR_STATE_MOVING_RIGHT;
|
||||
controlmap->controls[3].event_on = SDL_EVENT_KEY_DOWN;
|
||||
controlmap->controls[3].event_off = SDL_EVENT_KEY_UP;
|
||||
} CLEANUP {
|
||||
@@ -120,6 +120,6 @@ int main(void)
|
||||
LOG_ERROR(errctx);
|
||||
return 1;
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -532,6 +532,7 @@ int main(void)
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD),
|
||||
|
||||
102
tests/error.c
Normal file
102
tests/error.c
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* @file error.c
|
||||
* @brief Unit tests for the libakgl status band: reservation, ownership and names.
|
||||
*
|
||||
* The libakerror registry is process-global, so these tests assert against
|
||||
* whatever akgl_error_init() left behind rather than building their own state.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <akerror.h>
|
||||
|
||||
#include <akgl/error.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
/**
|
||||
* @brief akgl_error_init() must own the libakgl status band and name every code in it.
|
||||
*
|
||||
* A code whose name never registered degrades to "Unknown Error" in every stack
|
||||
* trace that carries it, and a band we never reserved is one another component
|
||||
* can name out from under us. Both stay silent until something has already gone
|
||||
* wrong, so assert them directly rather than waiting to read a useless trace.
|
||||
*/
|
||||
akerr_ErrorContext *test_error_init_owns_the_status_band(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
static const struct {
|
||||
int status;
|
||||
const char *name;
|
||||
} expected[] = {
|
||||
{ AKGL_ERR_SDL, "SDL Error" },
|
||||
{ AKGL_ERR_REGISTRY, "Registry Error" },
|
||||
{ AKGL_ERR_HEAP, "Heap Error" },
|
||||
{ AKGL_ERR_BEHAVIOR, "Behavior Error" },
|
||||
{ AKGL_ERR_LOGICINTERRUPT, "Logic Interrupt" }
|
||||
};
|
||||
bool named = true;
|
||||
int i = 0;
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(e, akgl_error_init());
|
||||
|
||||
TEST_ASSERT(e, (int)(sizeof(expected) / sizeof(expected[0])) == AKGL_ERR_COUNT,
|
||||
"the libakgl status band holds %d codes but %d are named here",
|
||||
AKGL_ERR_COUNT, (int)(sizeof(expected) / sizeof(expected[0])));
|
||||
|
||||
for ( i = 0; i < (int)(sizeof(expected) / sizeof(expected[0])); i++ ) {
|
||||
TEST_ASSERT_FLAG(named,
|
||||
strcmp(akerr_name_for_status(expected[i].status, NULL),
|
||||
expected[i].name) == 0);
|
||||
}
|
||||
TEST_ASSERT(e, named,
|
||||
"akgl_error_init did not register the expected name for every AKGL_ERR_* code");
|
||||
|
||||
// The reservation is what makes those names ours. Without it the
|
||||
// registrations above would still succeed for anyone who asked.
|
||||
TEST_EXPECT_STATUS(e, AKERR_STATUS_NAME_FOREIGN,
|
||||
akerr_register_status_name("not-libakgl", AKGL_ERR_HEAP, "Squatter"),
|
||||
"a foreign owner was allowed to rename a libakgl status");
|
||||
TEST_EXPECT_STATUS(e, AKERR_STATUS_RANGE_OVERLAP,
|
||||
akerr_reserve_status_range(AKGL_ERR_BASE, AKGL_ERR_COUNT, "not-libakgl"),
|
||||
"a foreign owner was allowed to reserve the libakgl status band");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calling akgl_error_init() twice must be a no-op, not a self-collision.
|
||||
*
|
||||
* Nothing in libakgl orders initialization for an embedding program, so a second
|
||||
* call has to be harmless. libakerror only treats an *identical* reservation as
|
||||
* a repeat -- a subset or superset raises -- which makes this a real constraint
|
||||
* on AKGL_ERR_BASE and AKGL_ERR_COUNT, not a triviality.
|
||||
*/
|
||||
akerr_ErrorContext *test_error_init_is_idempotent(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
|
||||
ATTEMPT {
|
||||
TEST_EXPECT_OK(e, akgl_error_init(), "the second akgl_error_init failed");
|
||||
TEST_EXPECT_OK(e, akgl_error_init(), "the third akgl_error_init failed");
|
||||
TEST_ASSERT(e, strcmp(akerr_name_for_status(AKGL_ERR_SDL, NULL), "SDL Error") == 0,
|
||||
"re-running akgl_error_init lost the name for AKGL_ERR_SDL");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, test_error_init_owns_the_status_band());
|
||||
CATCH(errctx, test_error_init_is_idempotent());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
}
|
||||
@@ -402,6 +402,7 @@ int main(void)
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
|
||||
@@ -361,6 +361,7 @@ int main(void)
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
|
||||
|
||||
@@ -347,6 +347,7 @@ int main(void)
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, load_fixture());
|
||||
|
||||
@@ -722,6 +722,7 @@ int main(void)
|
||||
SDL_SetHint(SDL_HINT_AUDIO_DRIVER, "dummy");
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, akgl_heap_init());
|
||||
CATCH(errctx, akgl_registry_init());
|
||||
CATCH(errctx, akgl_registry_init_properties());
|
||||
|
||||
@@ -17,7 +17,7 @@ akerr_ErrorContext *test_akgl_registry_init(RegistryFuncPtr funcptr)
|
||||
SDL_calloc_func calloc_func;
|
||||
SDL_realloc_func realloc_func;
|
||||
SDL_free_func free_func;
|
||||
|
||||
|
||||
SDL_GetMemoryFunctions(
|
||||
&malloc_func,
|
||||
&calloc_func,
|
||||
@@ -54,7 +54,7 @@ akerr_ErrorContext *test_akgl_registry_init_creation_failures(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
printf("Sucess\n");
|
||||
printf("Sucess\n");
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -62,7 +62,7 @@ akerr_ErrorContext *test_akgl_registry_init_creation_failures(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
printf("Sucess\n");
|
||||
printf("Sucess\n");
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -70,7 +70,7 @@ akerr_ErrorContext *test_akgl_registry_init_creation_failures(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
printf("Sucess\n");
|
||||
printf("Sucess\n");
|
||||
} FINISH(errctx, true);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -78,7 +78,7 @@ akerr_ErrorContext *test_akgl_registry_init_creation_failures(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
printf("Sucess\n");
|
||||
printf("Sucess\n");
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -87,6 +87,7 @@ int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, test_akgl_registry_init_creation_failures());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
|
||||
229
tests/sprite.c
229
tests/sprite.c
@@ -16,53 +16,53 @@
|
||||
|
||||
akerr_ErrorContext *test_akgl_spritesheet_initialize(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_SpriteSheet *sheet = NULL;
|
||||
SDL_Texture *image = NULL;
|
||||
akgl_String *tmpstr = NULL;
|
||||
// Does the image file get loaded?
|
||||
// Is the image file loaded correctly? (Surface comparison)
|
||||
// Is the spritesheet in the registry?
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_spritesheet(&sheet));
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
CATCH(errctx, akgl_spritesheet_initialize(sheet, 48, 48, "assets/spritesheet.png"));
|
||||
FAIL_ZERO_BREAK(errctx, sheet->texture, AKERR_VALUE, "akgl_spritesheet_initialize failed to load the sprite texture");
|
||||
FAIL_NONZERO_BREAK(
|
||||
errctx,
|
||||
((sheet->texture->w != 576) || (sheet->texture->h != 384)),
|
||||
AKERR_VALUE,
|
||||
"Loaded texture was not the correct size");
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
PREPARE_ERROR(errctx);
|
||||
akgl_SpriteSheet *sheet = NULL;
|
||||
SDL_Texture *image = NULL;
|
||||
akgl_String *tmpstr = NULL;
|
||||
// Does the image file get loaded?
|
||||
// Is the image file loaded correctly? (Surface comparison)
|
||||
// Is the spritesheet in the registry?
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_spritesheet(&sheet));
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
CATCH(
|
||||
errctx,
|
||||
akgl_render_and_compare(
|
||||
sheet->texture,
|
||||
image,
|
||||
0, 0, 576, 384,
|
||||
"test_spritesheet_loaded_image.png")
|
||||
);
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
CATCH(errctx, akgl_spritesheet_initialize(sheet, 48, 48, "assets/spritesheet.png"));
|
||||
FAIL_ZERO_BREAK(errctx, sheet->texture, AKERR_VALUE, "akgl_spritesheet_initialize failed to load the sprite texture");
|
||||
FAIL_NONZERO_BREAK(
|
||||
errctx,
|
||||
((sheet->texture->w != 576) || (sheet->texture->h != 384)),
|
||||
AKERR_VALUE,
|
||||
"Loaded texture was not the correct size");
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_GetPointerProperty(AKGL_REGISTRY_SPRITESHEET, "assets/spritesheet.png", NULL),
|
||||
AKERR_KEY,
|
||||
"Spritesheet was not placed in the registry");
|
||||
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_string(tmpstr));
|
||||
IGNORE(akgl_heap_release_spritesheet(sheet));
|
||||
if ( image != NULL )
|
||||
SDL_DestroyTexture(image);
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
|
||||
CATCH(
|
||||
errctx,
|
||||
akgl_render_and_compare(
|
||||
sheet->texture,
|
||||
image,
|
||||
0, 0, 576, 384,
|
||||
"test_spritesheet_loaded_image.png")
|
||||
);
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_GetPointerProperty(AKGL_REGISTRY_SPRITESHEET, "assets/spritesheet.png", NULL),
|
||||
AKERR_KEY,
|
||||
"Spritesheet was not placed in the registry");
|
||||
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_string(tmpstr));
|
||||
IGNORE(akgl_heap_release_spritesheet(sheet));
|
||||
if ( image != NULL )
|
||||
SDL_DestroyTexture(image);
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_sprite_initialize(void)
|
||||
@@ -71,28 +71,28 @@ akerr_ErrorContext *test_akgl_sprite_initialize(void)
|
||||
akgl_SpriteSheet *testsheet = NULL;
|
||||
akgl_Sprite *testsprite = NULL;
|
||||
akgl_String *tmpstr = NULL;
|
||||
|
||||
|
||||
// Does the sprite get loaded?
|
||||
// Do all frames of the sprite get loaded?
|
||||
// Are all the frames of the sprite what we expect? (Surface comparison)
|
||||
// Is the sprite added to the registry?
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_spritesheet(&testsheet));
|
||||
CATCH(errctx, akgl_heap_next_sprite(&testsprite));
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
CATCH(errctx, akgl_spritesheet_initialize(testsheet, 48, 48, "assets/spritesheet.png"));
|
||||
FAIL_ZERO_BREAK(errctx, testsheet, AKERR_VALUE, "akgl_spritesheet_initialize failed");
|
||||
CATCH(errctx, akgl_heap_next_spritesheet(&testsheet));
|
||||
CATCH(errctx, akgl_heap_next_sprite(&testsprite));
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
CATCH(errctx, akgl_spritesheet_initialize(testsheet, 48, 48, "assets/spritesheet.png"));
|
||||
FAIL_ZERO_BREAK(errctx, testsheet, AKERR_VALUE, "akgl_spritesheet_initialize failed");
|
||||
|
||||
CATCH(errctx, akgl_sprite_initialize(testsprite, "test", testsheet));
|
||||
FAIL_NONZERO_BREAK(errctx, (testsprite->sheet != testsheet), AKERR_VALUE, "Initialized sprite uses wrong sheet");
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "test", NULL),
|
||||
AKERR_KEY,
|
||||
"Sprite was not placed in the registry");
|
||||
|
||||
CATCH(errctx, akgl_sprite_initialize(testsprite, "test", testsheet));
|
||||
FAIL_NONZERO_BREAK(errctx, (testsprite->sheet != testsheet), AKERR_VALUE, "Initialized sprite uses wrong sheet");
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "test", NULL),
|
||||
AKERR_KEY,
|
||||
"Sprite was not placed in the registry");
|
||||
|
||||
} CLEANUP {
|
||||
IGNORE(akgl_heap_release_sprite(testsprite));
|
||||
IGNORE(akgl_heap_release_string(tmpstr));
|
||||
@@ -108,65 +108,65 @@ akerr_ErrorContext *test_akgl_sprite_load_json(void)
|
||||
akgl_Sprite *testsprite2 = NULL;
|
||||
akgl_String *tmpstr = NULL;
|
||||
SDL_Texture *image = NULL;
|
||||
|
||||
|
||||
// Does the sprite get loaded?
|
||||
// Do all frames of the sprite get loaded?
|
||||
// Are all the frames of the sprite what we expect? (Surface comparison)
|
||||
// Is the sprite added to the registry?
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json"));
|
||||
testsprite = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL);
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
testsprite,
|
||||
AKERR_KEY,
|
||||
"akgl_sprite_load_json succeeds but sprite is not placed in the registry");
|
||||
CATCH(errctx, akgl_heap_next_string(&tmpstr));
|
||||
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->width == 48), AKERR_VALUE, "width incorrect (48 : %d)", testsprite->width);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->height == 48), AKERR_VALUE, "height incorrect (48 : %d)", testsprite->height);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->speed == 100000000), AKERR_VALUE, "speed incorrect (100 : %d)", testsprite->speed);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->loop == true), AKERR_VALUE, "loop incorrect (1 : %d)", testsprite->loop);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->loopReverse == true), AKERR_VALUE, "loopReverse incorrect (1 : %d)", testsprite->loopReverse);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frames == 3), AKERR_VALUE, "frame count incorrect (3 : %d)", testsprite->frames);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frameids[0] == 12), AKERR_VALUE, "frameids[0] incorrect (12 : %d)", testsprite->frameids[0]);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frameids[1] == 13), AKERR_VALUE, "frameids[1] incorrect (13 : %d)", testsprite->frameids[1]);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frameids[2] == 14), AKERR_VALUE, "frameids[2] incorrect (14 : %d)", testsprite->frameids[2]);
|
||||
FAIL_NONZERO_BREAK(errctx, strcmp(testsprite->name, "testsprite"), AKERR_VALUE, "name incorrect (testsprite : %s)", (char *)testsprite->name);
|
||||
CATCH(errctx, akgl_sprite_load_json("assets/testsprite.json"));
|
||||
testsprite = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite", NULL);
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
testsprite,
|
||||
AKERR_KEY,
|
||||
"akgl_sprite_load_json succeeds but sprite is not placed in the registry");
|
||||
|
||||
// Is it using the right spritesheet?
|
||||
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->width == 48), AKERR_VALUE, "width incorrect (48 : %d)", testsprite->width);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->height == 48), AKERR_VALUE, "height incorrect (48 : %d)", testsprite->height);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->speed == 100000000), AKERR_VALUE, "speed incorrect (100 : %d)", testsprite->speed);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->loop == true), AKERR_VALUE, "loop incorrect (1 : %d)", testsprite->loop);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->loopReverse == true), AKERR_VALUE, "loopReverse incorrect (1 : %d)", testsprite->loopReverse);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frames == 3), AKERR_VALUE, "frame count incorrect (3 : %d)", testsprite->frames);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frameids[0] == 12), AKERR_VALUE, "frameids[0] incorrect (12 : %d)", testsprite->frameids[0]);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frameids[1] == 13), AKERR_VALUE, "frameids[1] incorrect (13 : %d)", testsprite->frameids[1]);
|
||||
FAIL_ZERO_BREAK(errctx, (testsprite->frameids[2] == 14), AKERR_VALUE, "frameids[2] incorrect (14 : %d)", testsprite->frameids[2]);
|
||||
FAIL_NONZERO_BREAK(errctx, strcmp(testsprite->name, "testsprite"), AKERR_VALUE, "name incorrect (testsprite : %s)", (char *)testsprite->name);
|
||||
|
||||
CATCH(
|
||||
errctx,
|
||||
akgl_render_and_compare(
|
||||
testsprite->sheet->texture,
|
||||
image,
|
||||
0, 0, 576, 384,
|
||||
"test_sprite_loaded_from_json_sheet.png"
|
||||
)
|
||||
);
|
||||
// Is it using the right spritesheet?
|
||||
|
||||
// If we load a second sprite using the same sheet name, do they use the same sheet in memory?
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testsprite2.json");
|
||||
CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json"));
|
||||
testsprite2 = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL);
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
testsprite,
|
||||
AKERR_KEY,
|
||||
"akgl_sprite_load_json succeeds but second sprite is not placed in the registry");
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/spritesheet.png");
|
||||
image = IMG_LoadTexture(renderer->sdl_renderer, (char *)&tmpstr->data);
|
||||
FAIL_ZERO_BREAK(errctx, image, AKGL_ERR_SDL, "Failed to load comparison image");
|
||||
|
||||
CATCH(
|
||||
errctx,
|
||||
akgl_render_and_compare(
|
||||
testsprite->sheet->texture,
|
||||
image,
|
||||
0, 0, 576, 384,
|
||||
"test_sprite_loaded_from_json_sheet.png"
|
||||
)
|
||||
);
|
||||
|
||||
// If we load a second sprite using the same sheet name, do they use the same sheet in memory?
|
||||
snprintf((char *)&tmpstr->data, AKGL_MAX_STRING_LENGTH, "%s%s", SDL_GetBasePath(), "assets/testsprite2.json");
|
||||
CATCH(errctx, akgl_sprite_load_json("assets/testsprite2.json"));
|
||||
testsprite2 = SDL_GetPointerProperty(AKGL_REGISTRY_SPRITE, "testsprite2", NULL);
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
testsprite,
|
||||
AKERR_KEY,
|
||||
"akgl_sprite_load_json succeeds but second sprite is not placed in the registry");
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
(testsprite->sheet == testsprite2->sheet),
|
||||
AKERR_VALUE,
|
||||
"Previously loaded spritesheets are not reused");
|
||||
|
||||
FAIL_ZERO_BREAK(
|
||||
errctx,
|
||||
(testsprite->sheet == testsprite2->sheet),
|
||||
AKERR_VALUE,
|
||||
"Previously loaded spritesheets are not reused");
|
||||
|
||||
} CLEANUP {
|
||||
if ( testsprite != NULL ) {
|
||||
IGNORE(akgl_heap_release_sprite(testsprite));
|
||||
@@ -185,16 +185,17 @@ akerr_ErrorContext *test_akgl_sprite_load_json(void)
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
renderer = &_akgl_renderer;
|
||||
|
||||
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 640, 480, 0, &window, &renderer->sdl_renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
@@ -8,19 +8,19 @@ void reset_string_heap(void);
|
||||
|
||||
akerr_ErrorContext *test_fresh_heap_gives_strings(void)
|
||||
{
|
||||
akgl_String *ptr = NULL;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_string(&ptr));
|
||||
} CLEANUP {
|
||||
reset_string_heap();
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
}
|
||||
akgl_String *ptr = NULL;
|
||||
|
||||
return 0;
|
||||
PREPARE_ERROR(errctx);
|
||||
for ( int i = 0; i < AKGL_MAX_HEAP_STRING - 1; i++ ) {
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_heap_next_string(&ptr));
|
||||
} CLEANUP {
|
||||
reset_string_heap();
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_string_heap_error_when_no_strings_left(void)
|
||||
@@ -95,7 +95,7 @@ akerr_ErrorContext *test_strcpy_to_all_strings_no_segfault(void)
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_akgl_string_initialize(void)
|
||||
{
|
||||
@@ -111,7 +111,7 @@ akerr_ErrorContext *test_akgl_string_initialize(void)
|
||||
CATCH(errctx, akgl_string_initialize(ptr, "Test value"));
|
||||
FAIL_NONZERO_BREAK(errctx, strcmp((char *)&ptr->data, "Test value"), AKERR_VALUE, "Expected 'Test value', got %s", (char *)&ptr->data);
|
||||
|
||||
CATCH(errctx, akgl_heap_release_string(NULL));
|
||||
CATCH(errctx, akgl_heap_release_string(NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Failure to properly handle NULL pointer");
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
@@ -130,9 +130,10 @@ void reset_string_heap(void)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
printf("test_fresh_heap_gives_string ....\n");
|
||||
test_fresh_heap_gives_strings();
|
||||
reset_string_heap();
|
||||
@@ -150,6 +151,6 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
#include <akgl/error.h>
|
||||
|
||||
/** @brief Fail the enclosing ATTEMPT block unless @p cond holds. */
|
||||
#define TEST_ASSERT(e, cond, ...) \
|
||||
if ( ! (cond) ) { \
|
||||
FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, __VA_ARGS__); \
|
||||
#define TEST_ASSERT(e, cond, ...) \
|
||||
if ( ! (cond) ) { \
|
||||
FAIL_BREAK(e, AKGL_ERR_BEHAVIOR, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,25 +33,25 @@
|
||||
* paths in a row without draining AKERR_ARRAY_ERROR. Pass 0 for @p expected to
|
||||
* require success.
|
||||
*/
|
||||
#define TEST_EXPECT_STATUS(e, expected, stmt, desc) \
|
||||
{ \
|
||||
akerr_ErrorContext *__tec = (stmt); \
|
||||
int __tst = ( __tec == NULL ) ? 0 : __tec->status; \
|
||||
if ( __tec != NULL ) { \
|
||||
__tec->handled = true; \
|
||||
__tec = akerr_release_error(__tec); \
|
||||
} \
|
||||
if ( __tst != (expected) ) { \
|
||||
FAIL_BREAK( \
|
||||
e, \
|
||||
AKGL_ERR_BEHAVIOR, \
|
||||
"%s: expected status %d (%s), got %d (%s)", \
|
||||
desc, \
|
||||
(int)(expected), \
|
||||
akerr_name_for_status((int)(expected), NULL), \
|
||||
__tst, \
|
||||
akerr_name_for_status(__tst, NULL)); \
|
||||
} \
|
||||
#define TEST_EXPECT_STATUS(e, expected, stmt, desc) \
|
||||
{ \
|
||||
akerr_ErrorContext *__tec = (stmt); \
|
||||
int __tst = ( __tec == NULL ) ? 0 : __tec->status; \
|
||||
if ( __tec != NULL ) { \
|
||||
__tec->handled = true; \
|
||||
__tec = akerr_release_error(__tec); \
|
||||
} \
|
||||
if ( __tst != (expected) ) { \
|
||||
FAIL_BREAK( \
|
||||
e, \
|
||||
AKGL_ERR_BEHAVIOR, \
|
||||
"%s: expected status %d (%s), got %d (%s)", \
|
||||
desc, \
|
||||
(int)(expected), \
|
||||
akerr_name_for_status((int)(expected), NULL), \
|
||||
__tst, \
|
||||
akerr_name_for_status(__tst, NULL)); \
|
||||
} \
|
||||
}
|
||||
|
||||
/** @brief Require that @p stmt succeeds, reporting @p desc if it does not. */
|
||||
|
||||
@@ -18,7 +18,7 @@ akerr_ErrorContext *test_tilemap_akgl_get_json_tilemap_property(void)
|
||||
json_error_t jsonerr;
|
||||
akgl_String *tmpstr = NULL;
|
||||
int propnum;
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
@@ -97,7 +97,7 @@ akerr_ErrorContext *test_akgl_tilemap_compute_tileset_offsets(void)
|
||||
&gamemap->tilesets[0].tile_offsets[3][1], // Tile 3 Y
|
||||
};
|
||||
int i = 0;
|
||||
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
gamemap->tilesets[0].tilecount = 4;
|
||||
gamemap->tilesets[0].columns = 2;
|
||||
@@ -194,7 +194,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
|
||||
json_error_t errdata;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
@@ -212,7 +212,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_layer_tile(void)
|
||||
(gamemap->layers[0].data[2] != 3) ||
|
||||
(gamemap->layers[0].data[3] != 4) ) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Test tilemap layer 0 tiles loaded with incorrect values (check gdb)");
|
||||
}
|
||||
}
|
||||
} CLEANUP {
|
||||
if ( pathstr != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(pathstr));
|
||||
@@ -234,7 +234,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_layers(void)
|
||||
int i = 0;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
@@ -267,13 +267,13 @@ akerr_ErrorContext *test_akgl_tilemap_load_layers(void)
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Map layer 2 should have 1 loaded object (testactor) and nothing else (see gdb)");
|
||||
}
|
||||
// Layer 1 and 3 should have no objects
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
|
||||
if ( gamemap->layers[0].objects[i].id != 0 ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
}
|
||||
}
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
|
||||
for ( i = 0; i < AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER ; i++ ) {
|
||||
if ( gamemap->layers[2].objects[i].id != 0 ) {
|
||||
FAIL(errctx, AKERR_VALUE, "Map layers 1 and 3 should have no objects loaded but found objects");
|
||||
goto _test_akgl_tilemap_load_layers_cleanup;
|
||||
@@ -291,7 +291,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_layers(void)
|
||||
) {
|
||||
FAIL_BREAK(errctx, AKERR_VALUE, "Map layers 1 and 3 should have tile data but it is incorrect");
|
||||
}
|
||||
_test_akgl_tilemap_load_layers_cleanup:
|
||||
_test_akgl_tilemap_load_layers_cleanup:
|
||||
} CLEANUP {
|
||||
if ( pathstr != NULL ) {
|
||||
IGNORE(akgl_heap_release_string(pathstr));
|
||||
@@ -313,7 +313,7 @@ akerr_ErrorContext *test_akgl_tilemap_load_tilesets(void)
|
||||
SDL_Texture *image = NULL;
|
||||
|
||||
memset((void *)gamemap, 0x00, sizeof(akgl_Tilemap));
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
@@ -385,7 +385,7 @@ akerr_ErrorContext *test_akgl_tilemap_load(void)
|
||||
akerr_ErrorContext *test_akgl_tilemap_draw(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
@@ -399,7 +399,7 @@ akerr_ErrorContext *test_akgl_tilemap_draw(void)
|
||||
akerr_ErrorContext *test_akgl_tilemap_draw_tileset(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
@@ -412,16 +412,17 @@ akerr_ErrorContext *test_akgl_tilemap_draw_tileset(void)
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
gamemap = &_akgl_gamemap;
|
||||
renderer = &_akgl_renderer;
|
||||
SDL_SetAppMetadata("SDL3-GameTest", "0.1", "net.aklabs.sdl3-gametest");
|
||||
|
||||
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO )) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't initialize SDL: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("net/aklabs/libakgl/test_sprite", 768, 576, 0, &window, &renderer->sdl_renderer)) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_SDL, "Couldn't create window/renderer: %s", SDL_GetError());
|
||||
}
|
||||
@@ -445,6 +446,5 @@ int main(void)
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
17
tests/util.c
17
tests/util.c
@@ -26,7 +26,7 @@ akerr_ErrorContext *test_akgl_rectangle_points_nullpointers(void)
|
||||
} HANDLE(errctx, AKERR_NULLPOINTER) {
|
||||
// noop
|
||||
} FINISH(errctx, true);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&points, NULL));
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "akgl_rectangle_points fails to FAIL with NULL RectanglePoints pointer");
|
||||
@@ -50,7 +50,7 @@ akerr_ErrorContext *test_akgl_rectangle_points_math(void)
|
||||
RectanglePoints points;
|
||||
SDL_FRect testrect = {.x = 0, .y = 0, .w = 32, .h = 32};
|
||||
memset((void *)&points, 0x00, sizeof(RectanglePoints));
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&points, &testrect));
|
||||
@@ -83,7 +83,7 @@ akerr_ErrorContext *test_akgl_collide_point_rectangle_nullpointers(void)
|
||||
point testpoint;
|
||||
RectanglePoints testrectpoints;
|
||||
bool testcollide;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -138,7 +138,7 @@ akerr_ErrorContext *test_akgl_collide_point_rectangle_logic(void)
|
||||
RectanglePoints testrectpoints;
|
||||
bool testcollide = false;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_rectangle_points(&testrectpoints, &testrect));
|
||||
CATCH(errctx, akgl_collide_point_rectangle(&testpoint, &testrectpoints, &testcollide));
|
||||
@@ -163,7 +163,7 @@ akerr_ErrorContext *test_akgl_collide_rectangles_nullpointers(void)
|
||||
SDL_FRect testrect1;
|
||||
SDL_FRect testrect2;
|
||||
bool testcollide;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
@@ -216,9 +216,9 @@ akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
||||
SDL_FRect testrect1 = { .x = 0, .y = 0, .w = 32, .h = 32};
|
||||
SDL_FRect testrect2 = { .x = 30, .y = 30, .w = 40, .h = 40};
|
||||
bool testcollide = false;
|
||||
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
// Collision overlapping on the top left
|
||||
CATCH(errctx, akgl_collide_rectangles(&testrect1, &testrect2, &testcollide));
|
||||
@@ -298,7 +298,7 @@ akerr_ErrorContext *test_akgl_collide_rectangles_logic(void)
|
||||
if ( testcollide == true ) {
|
||||
FAIL_BREAK(errctx, AKGL_ERR_BEHAVIOR, "Invalid collision reported");
|
||||
}
|
||||
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH(errctx, true);
|
||||
@@ -310,6 +310,7 @@ int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, test_akgl_rectangle_points_nullpointers());
|
||||
CATCH(errctx, test_akgl_rectangle_points_math());
|
||||
CATCH(errctx, test_akgl_collide_point_rectangle_nullpointers());
|
||||
|
||||
108
tests/version.c
Normal file
108
tests/version.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file version.c
|
||||
* @brief Unit tests for the version macros and the linked-library accessor.
|
||||
*
|
||||
* These assert that the several places the version appears cannot drift: the
|
||||
* string, the numeric components, and what the shared library reports at
|
||||
* runtime all come from one project() call, and this is what proves it.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <akerror.h>
|
||||
|
||||
#include <akgl/error.h>
|
||||
#include <akgl/version.h>
|
||||
|
||||
#include "testutil.h"
|
||||
|
||||
/**
|
||||
* @brief The version string and the numeric components must describe one version.
|
||||
*
|
||||
* They are separate substitutions in version.h.in, so a mangled template can
|
||||
* leave them disagreeing and nothing else would notice.
|
||||
*/
|
||||
akerr_ErrorContext *test_version_string_matches_components(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
char assembled[64];
|
||||
|
||||
ATTEMPT {
|
||||
snprintf(assembled, sizeof(assembled), "%d.%d.%d",
|
||||
AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, AKGL_VERSION_PATCH);
|
||||
TEST_ASSERT(e, strcmp(assembled, AKGL_VERSION) == 0,
|
||||
"AKGL_VERSION is \"%s\" but the components assemble to \"%s\"",
|
||||
AKGL_VERSION, assembled);
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The linked library must report the version its headers were generated from.
|
||||
*
|
||||
* In this build tree they are the same tree, so this can only fail if the test
|
||||
* picked up an installed libakgl off LD_LIBRARY_PATH instead of the one just
|
||||
* built -- which is precisely the mispairing the accessor exists to catch.
|
||||
*/
|
||||
akerr_ErrorContext *test_version_linked_matches_compiled(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
|
||||
ATTEMPT {
|
||||
TEST_ASSERT(e, akgl_version() != NULL, "akgl_version returned NULL");
|
||||
TEST_ASSERT(e, strcmp(akgl_version(), AKGL_VERSION) == 0,
|
||||
"linked libakgl reports \"%s\" but the headers say \"%s\"",
|
||||
akgl_version(), AKGL_VERSION);
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AKGL_VERSION_AT_LEAST must order versions correctly at the boundaries.
|
||||
*
|
||||
* Checked against the current version rather than fixed literals, so the test
|
||||
* does not have to be rewritten every time the version is bumped.
|
||||
*/
|
||||
akerr_ErrorContext *test_version_at_least_boundaries(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
|
||||
ATTEMPT {
|
||||
TEST_ASSERT(e,
|
||||
AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, AKGL_VERSION_PATCH),
|
||||
"AKGL_VERSION_AT_LEAST rejected the current version");
|
||||
TEST_ASSERT(e,
|
||||
!AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, AKGL_VERSION_PATCH + 1),
|
||||
"AKGL_VERSION_AT_LEAST accepted a later patch");
|
||||
TEST_ASSERT(e,
|
||||
!AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR + 1, 0),
|
||||
"AKGL_VERSION_AT_LEAST accepted a later minor");
|
||||
TEST_ASSERT(e,
|
||||
!AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR + 1, 0, 0),
|
||||
"AKGL_VERSION_AT_LEAST accepted a later major");
|
||||
TEST_ASSERT(e,
|
||||
AKGL_VERSION_AT_LEAST(AKGL_VERSION_MAJOR, AKGL_VERSION_MINOR, 0),
|
||||
"AKGL_VERSION_AT_LEAST rejected an earlier patch");
|
||||
} CLEANUP {
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
ATTEMPT {
|
||||
CATCH(errctx, akgl_error_init());
|
||||
CATCH(errctx, test_version_string_matches_components());
|
||||
CATCH(errctx, test_version_linked_matches_compiled());
|
||||
CATCH(errctx, test_version_at_least_boundaries());
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} FINISH_NORETURN(errctx);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
char *characterjson = NULL;
|
||||
char pathbuf[4096];
|
||||
char cwdbuf[1024];
|
||||
|
||||
|
||||
if ( argc < 3 ) {
|
||||
SDL_Log("charviewer [CHARACTER_FILE] [SPRITE ...]");
|
||||
return SDL_APP_FAILURE;
|
||||
@@ -39,9 +39,9 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
characterjson = argv[1];
|
||||
memset((char *)&pathbuf, 0x00, 4096);
|
||||
memset((char *)&cwdbuf, 0x00, 1024);
|
||||
|
||||
|
||||
ATTEMPT {
|
||||
|
||||
|
||||
FAIL_ZERO_BREAK(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate pointer");
|
||||
FAIL_ZERO_BREAK(errctx, getcwd((char *)&cwdbuf, 1024), AKERR_NULLPOINTER, "Couldn't get current working directory");
|
||||
|
||||
@@ -79,7 +79,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
memset((char *)&pathbuf, 0x00, 4096);
|
||||
}
|
||||
|
||||
ATTEMPT {
|
||||
ATTEMPT {
|
||||
if ( characterjson[0] != '/' ) {
|
||||
sprintf((char *)&pathbuf, "%s/%s", (char *)&cwdbuf, characterjson);
|
||||
} else {
|
||||
@@ -105,7 +105,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
SDL_Log("Opening gamepad %d", gamepadids[0]);
|
||||
FAIL_ZERO_BREAK(errctx, SDL_OpenGamepad(gamepadids[0]), AKGL_ERR_SDL, "%s", SDL_GetError());
|
||||
CATCH(errctx, akgl_controller_default(0, "player", 0, gamepadids[0]));
|
||||
|
||||
|
||||
} CLEANUP {
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
@@ -123,7 +123,7 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
|
||||
ATTEMPT {
|
||||
FAIL_ZERO_BREAK(errctx, appstate, AKERR_NULLPOINTER, "NULL appstate pointer");
|
||||
FAIL_ZERO_BREAK(errctx, event, AKERR_NULLPOINTER, "NULL event pointer");
|
||||
|
||||
|
||||
CATCH(errctx, akgl_controller_handle_event(appstate, event));
|
||||
if (event->type == SDL_EVENT_QUIT) {
|
||||
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
|
||||
@@ -140,7 +140,7 @@ SDL_AppResult SDL_AppIterate(void *appstate)
|
||||
akgl_Iterator opflags;
|
||||
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
|
||||
AKGL_BITMASK_CLEAR(opflags.flags);
|
||||
AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_UPDATE);
|
||||
AKGL_BITMASK_ADD(opflags.flags, AKGL_ITERATOR_OP_RENDER);
|
||||
|
||||
Reference in New Issue
Block a user