|
Banjo API 1.0.0-rc.2
Low-level C99 game development API
|
Macros | |
| #define | bj_error_code_kind(code) |
| #define | bj_error_code_is_user(code) |
Enumerations | |
| enum | bj_error_code { BJ_ERROR_NONE = 0x00000000 , BJ_ERROR = 0x00000001 , BJ_ERROR_UNSUPPORTED = 0x00000101 , BJ_ERROR_NOT_IMPLEMENTED = 0x00000201 , BJ_ERROR_SYSTEM = 0x00000002 , BJ_ERROR_FILE_NOT_FOUND = 0x00000102 , BJ_ERROR_CANNOT_ALLOCATE = 0x00000202 , BJ_ERROR_INITIALIZE = 0x00000302 , BJ_ERROR_DISPOSE = 0x00000402 , BJ_ERROR_IO = 0x00000003 , BJ_ERROR_CANNOT_READ = 0x00000103 , BJ_ERROR_CANNOT_WRITE = 0x00000203 , BJ_ERROR_INVALID_DATA = 0x00000004 , BJ_ERROR_INVALID_FORMAT = 0x00000104 , BJ_ERROR_INCORRECT_VALUE = 0x00000204 , BJ_ERROR_VIDEO = 0x00000005 , BJ_ERROR_AUDIO = 0x00000006 , BJ_ERROR_NETWORK = 0x00000007 , BJ_ERROR_NETWORK_SOCKET = 0x00000107 , BJ_ERROR_NETWORK_TIMEOUT = 0x00000207 } |
Functions | |
| void | bj_set_error (struct bj_error **error, uint32_t code, const char *message) |
| void | bj_set_error_fmt (struct bj_error **error, uint32_t code, const char *format,...) |
| void | bj_propagate_error (struct bj_error **dest, struct bj_error *src) |
| void | bj_propagate_prefixed_error (struct bj_error **dest, struct bj_error *src, const char *format,...) |
| void | bj_prefix_error (struct bj_error **error, const char *prefix) |
| void | bj_prefix_error_fmt (struct bj_error **error, const char *format,...) |
| struct bj_error * | bj_copy_error (const struct bj_error *error) |
| void | bj_clear_error (struct bj_error **error) |
| bj_bool | bj_error_matches (const struct bj_error *error, uint32_t code) |
| bj_bool | bj_error_matches_kind (const struct bj_error *error, uint32_t kind) |
| uint32_t | bj_error_code (const struct bj_error *error) |
| const char * | bj_error_message (const struct bj_error *error) |
Report and inspect things that went wrong, without exceptions.
C doesn't have exceptions. When a function can fail, it has to tell the caller two things: that it failed, and what went wrong. Banjo's convention is that the function's normal return value carries the result (the value you asked for, or a sentinel like NULL if there's no useful value), and a separate out-parameter at the end of the argument list carries the error details.
"Out-parameter" just means a pointer the caller hands in for the function to fill: the caller owns the storage, the function writes into it. Concretely, every fallible Banjo function has this shape:
The return value (bj_bitmap*) is what the caller wanted. The trailing bj_error** is the error sink. The caller chooses how much they care:
This is the same pattern GLib's GError uses, if you've seen that. The recipes section below shows it in action.
For a function declared bj_bool bj_do_thing(int arg, bj_error** error), the error parameter can be in one of three states, each meaningful:
| State | Meaning | Function behaviour |
|---|---|---|
| error == NULL | Caller does not want error details. | Zero-cost path: no allocation; in debug builds the error is logged then dropped. |
| error != NULL and *error == NULL | Caller wants to receive an error. | On failure, allocates a bj_error and stores it into *error. |
| error != NULL and *error != NULL | Caller already has an unhandled error. | First error wins: new error logged then dropped; original preserved. |
The first state is what makes the out-parameter cheap: passing NULL costs one pointer comparison. The third (first-error-wins) points at the root cause when several fallible calls appear without intermediate handling; later failures are usually cascading consequences.
An error returned through *error is caller-owned. Three functions move or destroy it:
Errors discarded by first-error-wins or NULL passing are freed by the library itself; the caller doesn't need to do anything.
Error codes are packed in three fields:
For example, BJ_ERROR_INCORRECT_VALUE is 0x00000204: origin 0x00 (Banjo), specific 0x0002, kind 0x04 (same as BJ_ERROR_INVALID_DATA).
This gives two useful matching strategies:
Applications can mint their own codes with a non-zero MSB without ever colliding with Banjo's reserved space. bj_error_code_is_user distinguishes them.
Take bj_error** as the last parameter. Use bj_set_error for literal messages and bj_set_error_fmt for formatted ones (don't snprintf into a buffer and then bj_set_error, that doubles the work and trips CERT-ERR33-C):
A chain of these produces a top-down readable message: "While initializing server: While loading config: file not found".
Both functions accept NULL and return false, so you can use them without a separate null check.
bj_error_code_is_user(code) returns non-zero exactly when the MSB is non-zero; kind-matching still works because the kind stays in the LSB.
mock_allocators will catch this.Discarding return values with (void). CERT-ERR33-C forbids this on any function that returns a status. Banjo's CI runs clang-tidy with cert checks as warnings-as-errors. Fold the return value into your control flow:
From the bj_error_code enum:
| Kind code | Mnemonic | Covers |
|---|---|---|
| 0x01 | BJ_ERROR | General / unspecified / unsupported / not-implemented |
| 0x02 | BJ_ERROR_SYSTEM | OS-level failures (alloc, init, dispose, file-not-found) |
| 0x03 | BJ_ERROR_IO | Read / write failures |
| 0x04 | BJ_ERROR_INVALID_DATA | Bad format, incorrect value |
| 0x05 | BJ_ERROR_VIDEO | Video subsystem (see Windows) |
| 0x06 | BJ_ERROR_AUDIO | Audio subsystem (see Audio) |
| 0x07 | BJ_ERROR_NETWORK | Network subsystem (incl. socket and timeout) |
BJ_ERROR_INITIALIZE (0x00000302) is the code you'll see when bj_begin_video or bj_begin_audio fail with "no suitable video" / "no suitable audio"; its kind is BJ_ERROR_SYSTEM, so a catch-all bj_error_matches_kind(err, BJ_ERROR_SYSTEM) catches it alongside file and allocation failures.
handling_errors.c walks through every recipe above. | #define bj_error_code_is_user | ( | code | ) |
Checks if an error code is user-defined (not from Banjo).
Banjo error codes have their most significant byte set to 0x00. User applications can use non-zero values in the MSB to define their own error codes without conflicting with Banjo.
| code | The error code to check. |
| #define bj_error_code_kind | ( | code | ) |
Extracts the kind (category) from an error code.
The kind is the least significant byte, shared by all errors in the same category. For example:
| code | The error code to extract the kind from. |
| enum bj_error_code |
A numeric representation of an error in Banjo.
Error codes are 32-bit values with a hierarchical structure:
For example, BJ_ERROR_INCORRECT_VALUE is 0x00000204:
Use bj_error_code_kind to extract the kind from any error code. Use bj_error_code_is_user to check if an error is user-defined.
| void bj_clear_error | ( | struct bj_error ** | error | ) |
Frees an error and sets the pointer to NULL.
Safe to call with a NULL pointer or pointer to NULL.
| error | Pointer to error to free. |
Referenced by demonstrate_error_copy(), demonstrate_error_matching(), and main().
Creates a copy of an error.
Returns a newly allocated error with the same code and message. The caller owns the copy and must free it with bj_clear_error.
| error | The error to copy. May be NULL. |
Referenced by demonstrate_error_copy().
| uint32_t bj_error_code | ( | const struct bj_error * | error | ) |
Gets the error code from an error object.
| error | The error to query. May be NULL. |
Checks if an error matches a specific error code.
Returns true if error is non-NULL and has the specified code. Use this for exact matching of error codes.
| error | The error to check. May be NULL. |
| code | The error code to match against. |
Referenced by demonstrate_error_matching().
Checks if an error belongs to a specific error kind (category).
Returns true if error is non-NULL and its kind matches the specified kind. Use this to handle categories of errors uniformly.
| error | The error to check. May be NULL. |
| kind | The error kind to match (e.g., BJ_ERROR_IO, BJ_ERROR_SYSTEM). |
Referenced by demonstrate_error_matching().
| const char * bj_error_message | ( | const struct bj_error * | error | ) |
Gets the error message from an error object.
The returned string is owned by the error object and remains valid until the error is freed or modified.
| error | The error to query. May be NULL. |
Referenced by demonstrate_error_copy(), demonstrate_error_matching(), and main().
| void bj_prefix_error | ( | struct bj_error ** | error, |
| const char * | prefix ) |
Adds a prefix to an existing error's message.
Prepends text to the error message to add context about where/why the error occurred.
If *error is NULL, this function does nothing.
| error | Pointer to the error to modify. |
| prefix | Null-terminated prefix string. |
| void bj_prefix_error_fmt | ( | struct bj_error ** | error, |
| const char * | format, | ||
| ... ) |
Adds a formatted prefix to an existing error's message.
Like bj_prefix_error, but with printf-style formatting.
| error | Pointer to the error to modify. |
| format | Printf-style format string for the prefix. |
| ... | Format arguments. |
Referenced by initialize_server().
Propagates an error to the caller's error location.
Transfers ownership of src to *dest. After this call, src is consumed and must not be used or freed.
If dest is NULL, the error is logged and freed (caller doesn't want it). If *dest is already non-NULL, src is logged and freed (first error wins).
Typical usage:
| dest | Caller's error location. May be NULL. |
| src | Error to propagate. Consumed by this call. |
Referenced by initialize_server().
| void bj_propagate_prefixed_error | ( | struct bj_error ** | dest, |
| struct bj_error * | src, | ||
| const char * | format, | ||
| ... ) |
Propagates an error with an added prefix.
Combines bj_prefix_error_fmt and bj_propagate_error in one call. Adds context to the error message before propagating.
| dest | Caller's error location. May be NULL. |
| src | Error to propagate. Consumed by this call. |
| format | Printf-style prefix format. |
| ... | Format arguments. |
Referenced by initialize_server().
| void bj_set_error | ( | struct bj_error ** | error, |
| uint32_t | code, | ||
| const char * | message ) |
Creates a new error with a literal message.
Allocates and initialises a new error object. Use this when the error message is a compile-time constant or doesn't need formatting.
If *error is already non-NULL, the new error is logged but not stored, preserving the original error (first error wins).
If error is NULL, the error is logged in debug builds but no allocation occurs (zero cost).
| error | Pointer to error location. May be NULL. |
| code | Error code from bj_error_code or user-defined. |
| message | Null-terminated error message. |
Referenced by load_config_file().
| void bj_set_error_fmt | ( | struct bj_error ** | error, |
| uint32_t | code, | ||
| const char * | format, | ||
| ... ) |
Creates a new error with a formatted message.
Like bj_set_error, but accepts printf-style format arguments.
| error | Pointer to error location. May be NULL. |
| code | Error code. |
| format | Printf-style format string. |
| ... | Format arguments. |
Referenced by open_network_port().