#ifndef ASSERTF_H #define ASSERTF_H /* Formatted assert. * * Asserts are a way to catch programmer errors at run-time. They should * be used when both of the following are true: * 1. It's impossible to catch the error at compile-time. * 2. The error is caused only by the programmer. * For example: * - removeobject for a local ID that isn't in the object event * templates is a programmer error because the object could never be * spawned. * - removeobject for a local ID that isn't spawned is not (necessarily) * a programmer error because the object could have been despawned by * the player. * - Trying to choose a move from the Fight menu when it's disabled is * not a programmer error because the player is able to try to choose * the move. * - The battle engine receiving a disabled move as the chosen move is * a programmer error because it should have been rejected by the UI. * * When possible, prefer to catch errors at compile-time with things * like STATIC_ASSERT rather than at run-time with assertf. * * assertf(cond); * assertf(cond) { recovery... } * assertf(cond, fmt, ...); * assertf(cond, fmt, ...) { recovery... } * * If cond is FALSE: * - In a release build: executes the recovery code (if any). * - In a debug build: shows a resumable crash screen and executes the * recovery code (if any). * - In a test build: causes the test to be INVALID. * * Usually the recovery code makes the function do nothing, for example: * - warp to a map that doesn't exist shouldn't warp anywhere. * - addobject of a local ID that doesn't exist shouldn't add anything. * * But sometimes the function has to return something, in which case the * recovery code does something that seems "reasonable", for example: * - CreateMonWithGenderNatureLetter should ignore an illegal gender or * letter. */ #define assertf(cond, ...) CAT(_ASSERTF, FIRST(__VA_OPT__(_FMT,) _COND))(cond __VA_OPT__(,) __VA_ARGS__) /* errorf(fmt, ...); * * Equivalent to assertf(FALSE, fmt, ...); * Useful for situations like: * if (cond1) * code1; * else if (cond2) * code2; * else * errorf("neither cond1 nor cond2"); * Or: * for (i = 0; i < n; i++) * { * if (array[i].member == value) * return i; * } * errorf("member not found"); */ #define errorf(fmt, ...) _ASSERTF_HANDLE("%s:%d: " fmt, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__) #define _ASSERTF_COND(cond) for (bool32 _recover = !(cond); _recover && (_ASSERTF_HANDLE("%s:%d: %s", __FILE__, __LINE__, STR(cond)), TRUE); _recover = FALSE) #define _ASSERTF_FMT(cond, fmt, ...) for (bool32 _recover = !(cond); _recover && (_ASSERTF_HANDLE("%s:%d: " fmt, __FILE__, __LINE__ __VA_OPT__(,) __VA_ARGS__), TRUE); _recover = FALSE) #if RELEASE #define _ASSERTF_HANDLE(...) (void)0 #elif TESTING #include "test_result.h" #define _ASSERTF_HANDLE(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, 0, fmt, __VA_ARGS__) #else #define _ASSERTF_HANDLE(fmt, ...) AssertfCrashScreen(__builtin_return_address(0), fmt, __VA_ARGS__) #endif void AssertfCrashScreen(const void *return0, const char *fmt, ...); #endif