From 6afb6d1cfad49c554dda29524b50428bc4a3d217 Mon Sep 17 00:00:00 2001 From: e2dk4r <43293320+e2dk4r@users.noreply.github.com> Date: Sat, 26 Oct 2024 01:10:53 +0300 Subject: [PATCH] wip - Remove printf() call on release builds. This is more deterministic than printf() modifiers. We only use it printing u64, f32 variables. - Add unit tests. - Add checking if there is enough stack memory available - Fixed trying to open gamepad when it is disconnecting --- .github/workflows/compile.yml | 2 +- .github/workflows/test.yml | 25 ++ include/assert.h | 15 + include/math.h | 57 ++++ include/memory.h | 131 ++++++++ include/text.h | 434 +++++++++++++++++++++++++++ include/type.h | 16 + meson.build | 7 + meson_options.txt | 1 + src/main.c | 548 +++++++++++++++++++++------------- src/text.h | 325 -------------------- test/memory_test.c | 137 +++++++++ test/meson.build | 5 + test/text_test.c | 497 ++++++++++++++++++++++++++++++ 14 files changed, 1673 insertions(+), 527 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 include/assert.h create mode 100644 include/math.h create mode 100644 include/memory.h create mode 100644 include/text.h create mode 100644 include/type.h create mode 100644 meson_options.txt delete mode 100644 src/text.h create mode 100644 test/memory_test.c create mode 100644 test/meson.build create mode 100644 test/text_test.c diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 57387e8..dbc99aa 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -15,7 +15,7 @@ jobs: - name: install packages run: sudo apt-get install -y --no-install-recommends --no-install-suggests meson ninja-build - libevdev-dev liburing-dev + liburing-dev libwayland-dev libwayland-client0 wayland-protocols - name: configure diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..30d2178 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: install packages + run: sudo apt-get install -y --no-install-recommends --no-install-suggests + meson ninja-build + liburing-dev + libwayland-dev libwayland-client0 wayland-protocols + + - name: configure + run: meson setup build --buildtype release -Dtest=true + + - name: test + run: meson test -C build diff --git a/include/assert.h b/include/assert.h new file mode 100644 index 0000000..2ef33a9 --- /dev/null +++ b/include/assert.h @@ -0,0 +1,15 @@ +#pragma once + +#if GAMEPAD_IDLE_INHIBIT_DEBUG +#define debug_assert(x) \ + if (!(x)) { \ + __builtin_debugtrap(); \ + } +#else +#define debug_assert(x) +#endif + +#define runtime_assert(x) \ + if (!(x)) { \ + __builtin_trap(); \ + } diff --git a/include/math.h b/include/math.h new file mode 100644 index 0000000..0e3b00b --- /dev/null +++ b/include/math.h @@ -0,0 +1,57 @@ +#pragma once + +#include "type.h" + +/* + * Returns binary logarithm of 𝑥. + * + * ctz(𝑥) 31^clz(𝑥) clz(𝑥) + * uint32 𝑥 bsf(𝑥) tzcnt(𝑥) ffs(𝑥) bsr(𝑥) lzcnt(𝑥) + * 0x00000000 wut 32 0 wut 32 + * 0x00000001 0 0 1 0 31 + * 0x80000001 0 0 1 31 0 + * 0x80000000 31 31 32 31 0 + * 0x00000010 4 4 5 4 27 + * 0x08000010 4 4 5 27 4 + * 0x08000000 27 27 28 27 4 + * 0xffffffff 0 0 1 31 0 + * + * @param x is a 64-bit integer + * @return number in range 0..63 or undefined if 𝑥 is 0 + * + * @note Adapted from https://github.com/jart/cosmopolitan/blob/master/libc/intrin/bsrl.c + * @copyright + * ╒══════════════════════════════════════════════════════════════════════════════╕ + * │ Copyright 2023 Justine Alexandra Roberts Tunney │ + * │ │ + * │ Permission to use, copy, modify, and/or distribute this software for │ + * │ any purpose with or without fee is hereby granted, provided that the │ + * │ above copyright notice and this permission notice appear in all copies. │ + * │ │ + * │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ + * │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ + * │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ + * │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ + * │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ + * │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ + * │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ + * │ PERFORMANCE OF THIS SOFTWARE. │ + * └──────────────────────────────────────────────────────────────────────────────┘ + */ +static inline u8 +bsrl(s64 x) +{ + static const u8 kDebruijn[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63, + }; + + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x |= x >> 32; + return kDebruijn[(x * 0x03f79d71b4cb0a89ull) >> 58]; +} diff --git a/include/memory.h b/include/memory.h new file mode 100644 index 0000000..a86d10f --- /dev/null +++ b/include/memory.h @@ -0,0 +1,131 @@ +#pragma once + +#include "assert.h" +#include "type.h" + +#if __has_builtin(__builtin_bzero) +#define bzero(address, size) __builtin_bzero(address, size) +#else +#error bzero must be supported by compiler +#endif + +#if __has_builtin(__builtin_alloca) +#define alloca(size) __builtin_alloca(size) +#else +#error alloca must be supported by compiler +#endif + +#if __has_builtin(__builtin_memcpy) +#define memcpy(dest, src, n) __builtin_memcpy(dest, src, n) +#else +#error memcpy must be supported by compiler +#endif + +struct memory_block { + void *block; + u64 used; + u64 total; +}; + +struct memory_chunk { + void *block; + u64 size; + u64 max; +}; + +struct memory_temp { + struct memory_block *memory; + u64 startedAt; +}; + +static void * +MemPush(struct memory_block *mem, u64 size) +{ + debug_assert(mem->used + size <= mem->total); + void *result = mem->block + mem->used; + mem->used += size; + return result; +} + +static struct memory_chunk * +MemPushChunk(struct memory_block *mem, u64 size, u64 max) +{ + struct memory_chunk *chunk = MemPush(mem, sizeof(*chunk) + max * sizeof(u8) + max * size); + chunk->block = chunk + sizeof(*chunk); + chunk->size = size; + chunk->max = max; + for (u64 index = 0; index < chunk->max; index++) { + u8 *flag = (u8 *)chunk->block + (sizeof(u8) * index); + *flag = 0; + } + return chunk; +} + +static inline b8 +MemChunkIsDataAvailableAt(struct memory_chunk *chunk, u64 index) +{ + u8 *flags = (u8 *)chunk->block; + return *(flags + index); +} + +static inline void * +MemChunkGetDataAt(struct memory_chunk *chunk, u64 index) +{ + void *dataBlock = (u8 *)chunk->block + chunk->max; + void *result = dataBlock + index * chunk->size; + return result; +} + +static void * +MemChunkPush(struct memory_chunk *chunk) +{ + void *result = 0; + void *dataBlock = chunk->block + sizeof(u8) * chunk->max; + for (u64 index = 0; index < chunk->max; index++) { + u8 *flag = chunk->block + sizeof(u8) * index; + if (*flag == 0) { + result = dataBlock + index * chunk->size; + *flag = 1; + return result; + } + } + + return result; +} + +static void +MemChunkPop(struct memory_chunk *chunk, void *block) +{ + void *dataBlock = chunk->block + sizeof(u8) * chunk->max; + debug_assert((block >= dataBlock && block <= dataBlock + (chunk->size * chunk->max)) && + "this block is not belong to this chunk"); + u64 index = (block - dataBlock) / chunk->size; + u8 *flag = chunk->block + sizeof(u8) * index; + *flag = 0; +} + +static struct memory_temp +MemTempBegin(struct memory_block *memory) +{ + return (struct memory_temp){ + .memory = memory, + .startedAt = memory->used, + }; +} + +static void +MemTempEnd(struct memory_temp *tempMemory) +{ + struct memory_block *memory = tempMemory->memory; + memory->used = tempMemory->startedAt; +} + +#include "text.h" +static struct string +MemPushString(struct memory_block *memory, u64 size) +{ + return (struct string){ + .value = MemPush(memory, size), + .length = size, + }; +} diff --git a/include/text.h b/include/text.h new file mode 100644 index 0000000..6bed211 --- /dev/null +++ b/include/text.h @@ -0,0 +1,434 @@ +#pragma once + +#include "assert.h" +#include "math.h" +#include "type.h" + +struct string { + u8 *value; + u64 length; +}; + +#define STRING_FROM_ZERO_TERMINATED(src) \ + ((struct string){ \ + .value = (u8 *)src, \ + .length = sizeof(src) - 1, \ + }) + +static inline struct string +StringFromZeroTerminated(u8 *src, u64 max) +{ + debug_assert(src != 0); + struct string string = {}; + + string.value = src; + + while (*src) { + string.length++; + if (string.length == max) + break; + src++; + } + + return string; +} + +static inline b8 +IsStringEqual(struct string *left, struct string *right) +{ + if (!left || !right || left->length != right->length) + return 0; + + for (u64 index = 0; index < left->length; index++) { + if (left->value[index] != right->value[index]) + return 0; + } + + return 1; +} + +static inline b8 +IsStringContains(struct string *string, struct string *search) +{ + if (!string || !search || string->length < search->length) + return 0; + + for (u64 stringIndex = 0; stringIndex < string->length; stringIndex++) { + b8 isFound = 1; + for (u64 searchIndex = 0, substringIndex = stringIndex; searchIndex < search->length; + searchIndex++, substringIndex++) { + b8 isEndOfString = substringIndex == string->length; + if (isEndOfString) { + isFound = 0; + break; + } + + b8 isCharactersNotMatching = string->value[substringIndex] != search->value[searchIndex]; + if (isCharactersNotMatching) { + isFound = 0; + break; + } + } + + if (isFound) + return 1; + } + + return 0; +} + +static inline b8 +IsStringStartsWith(struct string *string, struct string *search) +{ + if (!string || !search || string->length < search->length) + return 0; + + for (u64 searchIndex = 0; searchIndex < search->length; searchIndex++) { + b8 isCharactersNotMatching = string->value[searchIndex] != search->value[searchIndex]; + if (isCharactersNotMatching) + return 0; + } + + return 1; +} + +struct duration { + u64 ns; +}; + +#define DURATION_IN_SECONDS(seconds) ((struct duration){.ns = (seconds) * 1e9L}) +#define DURATION_IN_DAYS(days) ((struct duration){.ns = 1e9L * 60 * 60 * 24 * days}) +static inline b8 +ParseDuration(struct string *string, struct duration *duration) +{ + if (!string || string->length == 0 || string->length < 3) + return 0; + + // | Duration | Length | + // |----------|-------------| + // | ns | nanosecond | + // | us | microsecond | + // | ms | millisecond | + // | sec | second | + // | min | minute | + // | hr | hour | + // | day | day | + // | wk | week | + +#define UNIT_STRING(variableName, zeroTerminatedString) \ + static struct string variableName = { \ + .value = (u8 *)zeroTerminatedString, \ + .length = sizeof(zeroTerminatedString) - 1, \ + } + UNIT_STRING(nanosecondUnitString, "ns"); + UNIT_STRING(microsecondUnitString, "us"); + UNIT_STRING(millisocondUnitString, "ms"); + UNIT_STRING(secondUnitString, "sec"); + UNIT_STRING(minuteUnitString, "min"); + UNIT_STRING(hourUnitString, "hr"); + UNIT_STRING(dayUnitString, "day"); + UNIT_STRING(weekUnitString, "wk"); +#undef UNIT_STRING + + b8 isUnitExistsInString = + IsStringContains(string, &secondUnitString) || IsStringContains(string, &minuteUnitString) || + IsStringContains(string, &hourUnitString) || IsStringContains(string, &nanosecondUnitString) || + IsStringContains(string, µsecondUnitString) || IsStringContains(string, &millisocondUnitString) || + IsStringContains(string, &dayUnitString) || IsStringContains(string, &weekUnitString); + if (!isUnitExistsInString) { + return 0; + } + + struct duration parsed = {}; + u64 value = 0; + for (u64 index = 0; index < string->length; index++) { + u8 digitCharacter = string->value[index]; + b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; + if (!isDigit) { + // - get unit + struct string unitString = {.value = string->value + index, .length = string->length - index}; + if (/* unit: nanosecond */ IsStringStartsWith(&unitString, &nanosecondUnitString)) { + parsed.ns += value; + index += nanosecondUnitString.length - 1; + } else if (/* unit: microsecond */ IsStringStartsWith(&unitString, µsecondUnitString)) { + parsed.ns += value * 1e3L; + index += microsecondUnitString.length - 1; + } else if (/* unit: millisecond */ IsStringStartsWith(&unitString, &millisocondUnitString)) { + parsed.ns += value * 1e6L; + index += millisocondUnitString.length - 1; + } else if (/* unit: second */ IsStringStartsWith(&unitString, &secondUnitString)) { + parsed.ns += value * 1e9L; + index += secondUnitString.length - 1; + } else if (/* unit: minute */ IsStringStartsWith(&unitString, &minuteUnitString)) { + parsed.ns += value * 1e9L * 60; + index += minuteUnitString.length; + } else if (/* unit: hour */ IsStringStartsWith(&unitString, &hourUnitString)) { + parsed.ns += value * 1e9L * 60 * 60; + index += hourUnitString.length - 1; + } else if (/* unit: day */ IsStringStartsWith(&unitString, &dayUnitString)) { + parsed.ns += value * 1e9L * 60 * 60 * 24; + index += dayUnitString.length - 1; + } else if (/* unit: week */ IsStringStartsWith(&unitString, &weekUnitString)) { + parsed.ns += value * 1e9L * 60 * 60 * 24 * 7; + index += weekUnitString.length - 1; + } else { + // unsupported unit + return 0; + } + + // - reset value + value = 0; + continue; + } + + value *= 10; + u8 digit = digitCharacter - (u8)'0'; + value += digit; + } + + *duration = parsed; + + return 1; +} + +static inline b8 +IsDurationLessThan(struct duration *left, struct duration *right) +{ + return left->ns < right->ns; +} + +static inline b8 +IsDurationGraterThan(struct duration *left, struct duration *right) +{ + return left->ns > right->ns; +} + +static inline b8 +ParseU64(struct string *string, u64 *value) +{ + // max u64: 18446744073709551615 + if (!string || string->length > 20) + return 0; + + u64 parsed = 0; + for (u64 index = 0; index < string->length; index++) { + u8 digitCharacter = string->value[index]; + b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; + if (!isDigit) { + return 0; + } + + parsed *= 10; + u8 digit = digitCharacter - (u8)'0'; + parsed += digit; + } + + *value = parsed; + return 1; +} + +/* + * string buffer must at least able to hold 1 bytes, at most 20 bytes. + */ +static inline struct string +FormatU64(struct string *stringBuffer, u64 value) +{ + struct string result = {}; + if (!stringBuffer || stringBuffer->length == 0) + return result; + + // max u64: 18446744073709551615 + static const u64 powersOf10[20] = { + 1e00L, 1e01L, 1e02L, 1e03L, 1e04L, 1e05L, 1e06L, 1e07L, 1e08L, 1e09L, + 1e10L, 1e11L, 1e12L, 1e13L, 1e14L, 1e15L, 1e16L, 1e17L, 1e18L, 1e19L, + }; + u64 countOfDigits = 1; + while (countOfDigits < 20 && value > powersOf10[countOfDigits]) + countOfDigits++; + + if (countOfDigits > stringBuffer->length) + return result; + + u64 index = 0; + while (countOfDigits > 0) { + u64 power = powersOf10[countOfDigits - 1]; + u64 digit = value / power; + + // turn digit into character + stringBuffer->value[index] = digit + (u8)'0'; + + value -= digit * power; + + index++; + countOfDigits--; + } + + result.value = stringBuffer->value; + result.length = index; // written digits + return result; +} + +static inline struct string +FormatS64(struct string *stringBuffer, s64 value) +{ + struct string result = {}; + if (!stringBuffer || stringBuffer->length == 0) + return result; + + b8 isNegativeValue = value < 0; + if (isNegativeValue) { + value *= -1; + stringBuffer->value[0] = '-'; + stringBuffer->value += 1; + stringBuffer->length -= 1; + } + + result = FormatU64(stringBuffer, value); + return result; +} + +/* + * string buffer must at least able to hold 3 bytes. + * fractionCount [1,8] + */ +static inline struct string +FormatF32(struct string *stringBuffer, f32 value, u32 fractionCount) +{ + debug_assert(fractionCount >= 1 && fractionCount <= 8); + + struct string result = {}; + if (!stringBuffer || stringBuffer->length <= 3) + return result; + + // 1 - convert integer part to string + // assume value: 10.123 + // integerValue: 10 + struct string stringBufferForInteger = { + .value = stringBuffer->value, + .length = stringBuffer->length, + }; + + b8 isNegativeValue = value < 0; + if (isNegativeValue) { + value *= -1; + stringBufferForInteger.value[0] = '-'; + stringBufferForInteger.value += 1; + stringBufferForInteger.length -= 1; + } + + u32 integerValue = (u32)value; + struct string integerString = FormatU64(&stringBufferForInteger, (u64)integerValue); + if (integerString.length == 0) + return result; + + // 2 - insert point + stringBufferForInteger.value[integerString.length] = '.'; + + // 3 - convert fraction to string + struct string stringBufferForFraction = { + .value = stringBufferForInteger.value + integerString.length + 1, + .length = stringBufferForInteger.length - (integerString.length + 1), + }; + + // assume fractionCount = 2 + // 0.123 = 10.123 - 10.000 + // 12.30 = 0.123 * (10 ^ fractionCount) + // 12 = (int)12.30 + u64 fractionMultiplier = 10; + for (u32 fractionIndex = 1; fractionIndex < fractionCount; fractionIndex++) + fractionMultiplier *= 10; + + u32 fractionValue = (u32)((f32)(value - (f32)integerValue) * (f32)fractionMultiplier); + struct string fractionString = FormatU64(&stringBufferForFraction, fractionValue); + if (fractionString.length == 0) + return result; + + while (fractionString.length < fractionCount) { + fractionString.value[fractionString.length] = '0'; + fractionString.length++; + } + + result.value = stringBuffer->value; + result.length = isNegativeValue + integerString.length + 1 + fractionString.length; + return result; +} + +/* + * + * Converts unsigned 64-bit integer to hex string. + * + * @param stringBuffer needs at least 18 bytes + * @return sub string from stringBuffer, returns 0 on string.value on failure + * + * @note Adapted from https://github.com/jart/cosmopolitan/blob/master/libc/intrin/formathex64.c + * @copyright + * ╒══════════════════════════════════════════════════════════════════════════════╕ + * │ Copyright 2023 Justine Alexandra Roberts Tunney │ + * │ │ + * │ Permission to use, copy, modify, and/or distribute this software for │ + * │ any purpose with or without fee is hereby granted, provided that the │ + * │ above copyright notice and this permission notice appear in all copies. │ + * │ │ + * │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ + * │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ + * │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ + * │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ + * │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ + * │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ + * │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ + * │ PERFORMANCE OF THIS SOFTWARE. │ + * └──────────────────────────────────────────────────────────────────────────────┘ + */ +static inline struct string +FormatHex(struct string *stringBuffer, u64 value) +{ + struct string result = {}; + if (!stringBuffer || stringBuffer->length < 18) + return result; + + if (value == 0) { + // edge case 0x00 + u8 *digit = stringBuffer->value; + *digit++ = '0'; + *digit++ = 'x'; + *digit++ = '0'; + *digit++ = '0'; + result.value = stringBuffer->value; + result.length = 4; + return result; + } + + u64 index = 0; + stringBuffer->value[index] = '0'; + index++; + stringBuffer->value[index] = 'x'; + index++; + + // 1 - pick good width + u64 width; + { + u8 n = bsrl(value); + if (n < 16) { + if (n < 8) + width = 8; + else + width = 16; + } else { + if (n < 32) + width = 32; + else + width = 64; + } + } + + // 2 - turn value into hex + do { + width -= 4; + stringBuffer->value[index] = "0123456789abcdef"[(value >> width) & 15]; + index++; + } while (width); + + result.value = stringBuffer->value; + result.length = index; + return result; +} diff --git a/include/type.h b/include/type.h new file mode 100644 index 0000000..af48bfa --- /dev/null +++ b/include/type.h @@ -0,0 +1,16 @@ +#pragma once + +typedef __INT8_TYPE__ s8; +typedef __INT16_TYPE__ s16; +typedef __INT32_TYPE__ s32; +typedef __INT64_TYPE__ s64; + +typedef __UINT8_TYPE__ u8; +typedef __UINT16_TYPE__ u16; +typedef __UINT32_TYPE__ u32; +typedef __UINT64_TYPE__ u64; +typedef u8 b8; + +typedef float f32; +typedef double f64; + diff --git a/meson.build b/meson.build index 7eaf538..1b21c33 100644 --- a/meson.build +++ b/meson.build @@ -14,6 +14,8 @@ add_project_arguments( '-funroll-loops', '-fomit-frame-pointer', + '-Wno-unused-function', + '-DCOMPILER_GCC=' + (cc.get_id() == 'gcc').to_int().to_string(), '-DCOMPILER_CLANG=' + (cc.get_id() == 'clang').to_int().to_string(), '-DGAMEPAD_IDLE_INHIBIT_DEBUG=' + is_build_debug.to_int().to_string(), @@ -34,9 +36,14 @@ sources = files([ executable( 'gamepad_idle_inhibit', sources: sources + wl_protocols_src, + include_directories: 'include', dependencies: [ liburing, wayland_client, ], install: true, ) + +if get_option('test') + subdir('test') +endif diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..604b860 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1 @@ +option('test', type: 'boolean', value: true) diff --git a/src/main.c b/src/main.c index 915b187..b2adf1e 100644 --- a/src/main.c +++ b/src/main.c @@ -2,41 +2,24 @@ #define _XOPEN_SOURCE 700 #include -#include #include #include -#include -#include #include #include #include +#include #include +#include "assert.h" +#include "memory.h" +#include "text.h" +#include "type.h" + // NOTE: CI fix #ifndef IORING_ASYNC_CANCEL_ALL #define IORING_ASYNC_CANCEL_ALL (1U << 0) #endif -#if __has_builtin(__builtin_alloca) -#define alloca(size) __builtin_alloca(size) -#else -#error alloca must be supported -#endif - -#if GAMEPAD_IDLE_INHIBIT_DEBUG -#define debug_assert(x) \ - if (!(x)) { \ - __builtin_debugtrap(); \ - } -#else -#define debug_assert(x) -#endif - -#define runtime_assert(x) \ - if (!(x)) { \ - __builtin_trap(); \ - } - #define ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) #include "idle-inhibit-unstable-v1-client-protocol.h" @@ -45,20 +28,6 @@ #define POLLPRI 0x002 /* There is urgent data to read. */ #define POLLOUT 0x004 /* Writing now will not block. */ -typedef __INT8_TYPE__ s8; -typedef __INT16_TYPE__ s16; -typedef __INT32_TYPE__ s32; -typedef __INT64_TYPE__ s64; - -typedef __UINT8_TYPE__ u8; -typedef __UINT16_TYPE__ u16; -typedef __UINT32_TYPE__ u32; -typedef __UINT64_TYPE__ u64; -typedef u8 b8; - -typedef float f32; -typedef double f64; - #define OP_DEVICE_OPEN (1 << 0) #define OP_JOYSTICK_POLL (1 << 1) #define OP_JOYSTICK_READ (1 << 2) @@ -106,6 +75,11 @@ struct op { u8 type; }; +struct op_device_open { + u8 type; + struct string path; +}; + struct op_global { }; @@ -168,6 +142,7 @@ struct op_joystick_poll { s32 triggerRange; s32 triggerMinimum; struct gamepad *gamepad; + struct string path; }; struct op_joystick_read { @@ -176,73 +151,10 @@ struct op_joystick_read { struct op_joystick_poll *op_joystick_poll; }; -struct memory_block { - void *block; - u64 used; - u64 total; -}; - -struct memory_chunk { - void *block; - u64 size; - u64 max; -}; - #define KILOBYTES (1 << 10) #define MEGABYTES (1 << 20) #define GIGABYTES (1 << 30) -static void * -mem_chunk_push(struct memory_chunk *chunk) -{ - debug("mem_chunk_push\n"); - void *result = 0; - void *dataBlock = chunk->block + sizeof(u8) * chunk->max; - for (u64 index = 0; index < chunk->max; index++) { - u8 *flag = chunk->block + sizeof(u8) * index; - if (*flag == 0) { - result = dataBlock + index * chunk->size; - *flag = 1; - return result; - } - } - - return result; -} - -static void -mem_chunk_pop(struct memory_chunk *chunk, void *block) -{ - debug("mem_chunk_pop\n"); - void *dataBlock = chunk->block + sizeof(u8) * chunk->max; - u64 index = (block - dataBlock) / chunk->size; - u8 *flag = chunk->block + sizeof(u8) * index; - *flag = 0; -} - -static void * -mem_push(struct memory_block *mem, u64 size) -{ - debug_assert(mem->used + size <= mem->total); - void *result = mem->block + mem->used; - mem->used += size; - return result; -} - -static struct memory_chunk * -mem_push_chunk(struct memory_block *mem, u64 size, u64 max) -{ - struct memory_chunk *chunk = mem_push(mem, sizeof(*chunk) + max * sizeof(u8) + max * size); - chunk->block = chunk + sizeof(*chunk); - chunk->size = size; - chunk->max = max; - for (u64 index = 0; index < chunk->max; index++) { - u8 *flag = chunk->block + sizeof(u8) * index; - *flag = 0; - } - return chunk; -} - struct wl_context { struct wl_display *wl_display; struct wl_compositor *wl_compositor; @@ -255,11 +167,11 @@ static void wl_registry_global(void *data, struct wl_registry *wl_registry, u32 name, const char *interface, u32 version) { struct wl_context *context = data; - if (strcmp(interface, wl_compositor_interface.name) == 0) { - context->wl_compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, version); - } - else if (strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) == 0) { + struct string interfaceString = StringFromZeroTerminated((u8 *)interface, 64); + if (IsStringEqual(&interfaceString, &STRING_FROM_ZERO_TERMINATED("wl_compositor"))) { + context->wl_compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, version); + } else if (IsStringEqual(&interfaceString, &STRING_FROM_ZERO_TERMINATED("zwp_idle_inhibit_manager_v1"))) { context->zwp_idle_inhibit_manager_v1 = wl_registry_bind(wl_registry, name, &zwp_idle_inhibit_manager_v1_interface, version); } @@ -273,38 +185,25 @@ wl_registry_global_remove(void *data, struct wl_registry *wl_registry, u32 name) static const struct wl_registry_listener wl_registry_listener = {.global = wl_registry_global, .global_remove = wl_registry_global_remove}; -#include "text.h" - int main(int argc, char *argv[]) { int error_code = 0; struct wl_context context = {}; - struct duration timeout = (struct duration){.ns = 30 * 1e9L}; + struct duration timeout = DURATION_IN_SECONDS(30); #if GAMEPAD_IDLE_INHIBIT_DEBUG - timeout = (struct duration){.ns = 3 * 1e9L}; + timeout = DURATION_IN_SECONDS(3); #endif u32 maxGamepadCount = 4; // parse commandline arguments for (u64 argumentIndex = 1; argumentIndex < argc; argumentIndex++) { - struct string argument = StringFromZeroTerminated((u8 *)argv[argumentIndex]); - -#define ARGUMENT_STRING(variableName, zeroTerminatedString) \ - static struct string variableName = { \ - .data = (u8 *)zeroTerminatedString, \ - .len = sizeof(zeroTerminatedString) - 1, \ - } - ARGUMENT_STRING(argumentTimeoutString, "--timeout"); - ARGUMENT_STRING(argumentMaxGamepadCountString, "--max-gamepad-count"); - ARGUMENT_STRING(argumentHelpShortString, "-h"); - ARGUMENT_STRING(argumentHelpString, "--help"); -#undef ARGUMENT_STRING + struct string argument = StringFromZeroTerminated((u8 *)argv[argumentIndex], 1024); // --timeout - if (IsStringEqual(&argument, &argumentTimeoutString)) { + if (IsStringEqual(&argument, &STRING_FROM_ZERO_TERMINATED("--timeout"))) { b8 isArgumentWasLast = argumentIndex + 1 == argc; if (isArgumentWasLast) { fatal("timeout value is missing\n"); @@ -312,18 +211,16 @@ main(int argc, char *argv[]) } argumentIndex++; - struct string timeoutString = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + struct string timeoutString = StringFromZeroTerminated((u8 *)argv[argumentIndex], 1024); struct duration parsed; - struct duration oneSecond = (struct duration){.ns = 1 * 1e9L}; - struct duration oneDay = (struct duration){.ns = 60 * 60 * 24 * 1 * 1e9L}; if (!ParseDuration(&timeoutString, &parsed)) { fatal("timeout must be positive number\n"); return GAMEPAD_ERROR_ARGUMENT_MUST_BE_POSITIVE_NUMBER; - } else if (IsDurationLessThan(&parsed, &oneSecond)) { + } else if (IsDurationLessThan(&parsed, &DURATION_IN_SECONDS(1))) { fatal("timeout must be bigger or equal than 1 second\n"); return GAMEPAD_ERROR_ARGUMENT_MUST_BE_GRATER; - } else if (IsDurationGraterThan(&parsed, &oneDay)) { + } else if (IsDurationGraterThan(&parsed, &DURATION_IN_DAYS(1))) { fatal("timeout must be less or equal 1 day\n"); return GAMEPAD_ERROR_ARGUMENT_MUST_BE_LESS; } @@ -332,7 +229,7 @@ main(int argc, char *argv[]) } // --max-gamepad-count - else if (IsStringEqual(&argument, &argumentMaxGamepadCountString)) { + else if (IsStringEqual(&argument, &STRING_FROM_ZERO_TERMINATED("--max-gamepad-count"))) { b8 isArgumentWasLast = argumentIndex + 1 == argc; if (isArgumentWasLast) { fatal("max-gamepad-count value is missing\n"); @@ -340,7 +237,7 @@ main(int argc, char *argv[]) } argumentIndex++; - struct string maxGamepadCountString = StringFromZeroTerminated((u8 *)argv[argumentIndex]); + struct string maxGamepadCountString = StringFromZeroTerminated((u8 *)argv[argumentIndex], 1024); if (!ParseU64(&maxGamepadCountString, (u64 *)&maxGamepadCount)) { fatal("max-gamepad-count must be positive number\n"); return GAMEPAD_ERROR_ARGUMENT_MUST_BE_POSITIVE_NUMBER; @@ -354,18 +251,19 @@ main(int argc, char *argv[]) } // -h, --help - else if (IsStringEqual(&argument, &argumentHelpShortString) || IsStringEqual(&argument, &argumentHelpString)) { + else if (IsStringEqual(&argument, &STRING_FROM_ZERO_TERMINATED("-h")) || + IsStringEqual(&argument, &STRING_FROM_ZERO_TERMINATED("--help"))) { static struct string helpString = { #define HELP_STRING_TEXT \ - "NAME: " \ + "NAME:" \ "\n" \ " gamepad_idle_inhibit - prevent idling wayland on controllers button presses" \ "\n\n" \ - "SYNOPSIS: " \ + "SYNOPSIS:" \ "\n" \ " gamepad_idle_inhibit [OPTION]..." \ "\n\n" \ - "DESCIPTION: " \ + "DESCIPTION:" \ "\n" \ " -t, --timeout [1sec,1day]\n" \ " How much time need to elapse to idle.\n" \ @@ -385,18 +283,18 @@ main(int argc, char *argv[]) " How many gamepads need to be tracked.\n" \ " Default is 4." \ "\n\n" - .data = (u8 *)HELP_STRING_TEXT, - .len = sizeof(HELP_STRING_TEXT) - 1, + .value = (u8 *)HELP_STRING_TEXT, + .length = sizeof(HELP_STRING_TEXT) - 1, #undef HELP_STRING_TEXT }; - write(STDOUT_FILENO, helpString.data, helpString.len); + write(STDOUT_FILENO, helpString.value, helpString.length); return 0; } // unknown argument else { write(STDERR_FILENO, "e: Unknown '", 12); - write(STDERR_FILENO, argument.data, argument.len); + write(STDERR_FILENO, argument.value, argument.length); write(STDERR_FILENO, "' argument\n", 11); return GAMEPAD_ERROR_ARGUMENT_UNKNOWN; } @@ -431,30 +329,81 @@ main(int argc, char *argv[]) context.wl_surface = wl_compositor_create_surface(context.wl_compositor); /* memory */ - struct memory_block memory_block = {}; + struct memory_block memory = {}; // TODO: tune total used memory according to arguments - memory_block.total = 1 * KILOBYTES; - // TODO: check stack memory is enough - memory_block.block = alloca(memory_block.total); - bzero(memory_block.block, memory_block.total); - // mmap(0, (size_t)memory_block.total, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (!memory_block.block) { - fatal("you do not have 1k memory available.\n"); - error_code = GAMEPAD_ERROR_MEMORY; - goto wayland_exit; + memory.total = 1 * KILOBYTES; + + // A - allocate from stack + { + // - check limit + struct rlimit rlim; + if (getrlimit(RLIMIT_STACK, &rlim)) { + fatal("cannot check stack limit\n"); + error_code = GAMEPAD_ERROR_MEMORY; + goto wayland_exit; + } + + // (lldb) p rlim + // (rlimit) (rlim_cur = 8388608, rlim_max = 18446744073709551615) + if (rlim.rlim_cur < (4 * KILOBYTES) + memory.total) { + fatal("you do not have 1k memory available.\n"); + error_code = GAMEPAD_ERROR_MEMORY; + goto wayland_exit; + } + + // - allocate + memory.block = alloca(memory.total); + + // - initialize to zero + bzero(memory.block, memory.total); } - struct gamepad *gamepads = mem_push(&memory_block, maxGamepadCount * sizeof(*gamepads)); + // B - Allocate from RAM + { + // mmap(0, (size_t)memory.total, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + // if (!memory.block) { + // fatal("you do not have 1k memory available.\n"); + // error_code = GAMEPAD_ERROR_MEMORY; + // goto wayland_exit; + // } + } - struct memory_chunk *MemoryForDeviceOpenEvents = mem_push_chunk(&memory_block, sizeof(struct op), maxGamepadCount); + struct gamepad *gamepads = MemPush(&memory, maxGamepadCount * sizeof(*gamepads)); + + struct memory_chunk *MemoryForDeviceOpenEvents = + MemPushChunk(&memory, sizeof(struct op_device_open), maxGamepadCount); struct memory_chunk *MemoryForJoystickPollEvents = - mem_push_chunk(&memory_block, sizeof(struct op_joystick_poll), maxGamepadCount); + MemPushChunk(&memory, sizeof(struct op_joystick_poll), maxGamepadCount); struct memory_chunk *MemoryForJoystickReadEvents = - mem_push_chunk(&memory_block, sizeof(struct op_joystick_read), maxGamepadCount); + MemPushChunk(&memory, sizeof(struct op_joystick_read), maxGamepadCount); + + struct string stdoutBuffer = MemPushString(&memory, 256); + struct string stringBuffer = MemPushString(&memory, 32); #if GAMEPAD_IDLE_INHIBIT_DEBUG - printf("total memory usage (in bytes): %lu\n", memory_block.used); - printf("total memory wasted (in bytes): %lu\n", memory_block.total - memory_block.used); + { + struct string string; + u64 length = 0; + +#define PRINTLN_U64(prefix, number) \ + string = (struct string){.value = (u8 *)prefix, .length = sizeof(prefix) - 1}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatU64(&stringBuffer, number); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = (struct string){.value = (u8 *)"\n", .length = 1}; \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + + PRINTLN_U64("total memory usage (in bytes): ", memory.used); + PRINTLN_U64("total memory wasted (in bytes): ", memory.total - memory.used); +#undef PRINTLN_U64 + + // print buffered string to output + debug_assert(length <= stdoutBuffer.length); + write(STDOUT_FILENO, stdoutBuffer.value, length); + } #endif /* io_uring */ @@ -528,17 +477,33 @@ main(int argc, char *argv[]) if (dirent->d_type != DT_CHR) continue; - char *path = dirent->d_name; - if (!(path[0] == 'e' && path[1] == 'v' && path[2] == 'e' && path[3] == 'n' && path[4] == 't')) - continue; - struct op_joystick_poll stagedOp = { .type = OP_JOYSTICK_POLL, + .path = StringFromZeroTerminated((u8 *)dirent->d_name, 1024), }; + if (!IsStringStartsWith(&stagedOp.path, &STRING_FROM_ZERO_TERMINATED("event"))) + continue; - stagedOp.fd = openat(inputDirFd, path, O_RDONLY | O_NONBLOCK); + stagedOp.fd = openat(inputDirFd, (char *)stagedOp.path.value, O_RDONLY | O_NONBLOCK); if (stagedOp.fd == -1) { - warning("cannot open some event file\n"); + // warning("cannot open some event file\n"); + struct string string; + u64 length = 0; + + string = STRING_FROM_ZERO_TERMINATED("Cannot open device. event: "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = stagedOp.path; + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED("\n"); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + debug_assert(length <= stdoutBuffer.length); + write(STDOUT_FILENO, stdoutBuffer.value, length); continue; } @@ -585,7 +550,42 @@ main(int argc, char *argv[]) stagedOp.triggerRange = triggerAbsInfo.maximum - triggerAbsInfo.minimum; stagedOp.triggerMinimum = triggerAbsInfo.minimum; - printf("Input device ID: bus %#x vendor %#x product %#x\n", id.bustype, id.vendor, id.product); + { + struct string string; + u64 length = 0; + + string = STRING_FROM_ZERO_TERMINATED("Gamepad connected @ bus "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.bustype); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED(" vendor "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.vendor); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED(" product "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.product); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED("\n"); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + debug_assert(length <= stdoutBuffer.length); + write(STDOUT_FILENO, stdoutBuffer.value, length); + } + stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxGamepadCount); if (!stagedOp.gamepad) { warning("Maximum number of gamepads connected! So not registering this one.\n"); @@ -595,7 +595,8 @@ main(int argc, char *argv[]) stagedOp.gamepad->isConnected = 1; // - Queue poll on gamepad for input event - struct op_joystick_poll *submitOp = mem_chunk_push(MemoryForJoystickPollEvents); + struct op_joystick_poll *submitOp = MemChunkPush(MemoryForJoystickPollEvents); + debug_assert(submitOp); *submitOp = stagedOp; struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); io_uring_prep_poll_add(sqe, submitOp->fd, POLLIN); @@ -629,9 +630,30 @@ main(int argc, char *argv[]) if (error == EAGAIN || error == EINTR) goto wait; fatal("io_uring\n"); + #if GAMEPAD_IDLE_INHIBIT_DEBUG - printf("errno: %d %s\n", -error, strerror(-error)); + // printf("errno: %d %s\n", -error, strerror(-error)); + u64 length = 0; + struct string string; + + // see: /usr/include/asm-generic/errno-base.h + // /usr/include/asm-generic/errno.h + string = STRING_FROM_ZERO_TERMINATED("errno: "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatU64(&stdoutBuffer, error); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED("\n"); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + debug_assert(length <= stdoutBuffer.length); + write(STDOUT_FILENO, stdoutBuffer.value, length); #endif + error_code = GAMEPAD_ERROR_IO_URING_WAIT; break; } @@ -696,9 +718,13 @@ main(int argc, char *argv[]) if (event->mask & IN_ISDIR) goto cqe_seen; - char *path = event->name; - if (!(path[0] == 'e' && path[1] == 'v' && path[2] == 'e' && path[3] == 'n' && path[4] == 't')) - continue; + struct op_device_open stagedOp = { + .type = OP_DEVICE_OPEN, + .path = StringFromZeroTerminated((u8 *)event->name, 1024), + }; + + if (!IsStringStartsWith(&stagedOp.path, &STRING_FROM_ZERO_TERMINATED("event"))) + goto cqe_seen; // printf("--> %d %s %s\n", event->mask, event->name, path); // NOTE: When gamepad connects, we have to wait for udev @@ -712,26 +738,39 @@ main(int argc, char *argv[]) // When gamepad disconnects: // 0015 event20 mask: IN_ATTRIB - // TODO: Do not request openat() when gamepad disconnects! + // - Do not request openat() when gamepad disconnects! + { + for (u64 pollEventIndex = 0; pollEventIndex < MemoryForJoystickPollEvents->max; pollEventIndex++) { + if (!MemChunkIsDataAvailableAt(MemoryForJoystickPollEvents, pollEventIndex)) + continue; + struct op_joystick_poll *op_joystick_poll = MemChunkGetDataAt(MemoryForJoystickPollEvents, pollEventIndex); + if (IsStringEqual(&stagedOp.path, &op_joystick_poll->path)) + goto cqe_seen; + } + } if (!(event->mask & IN_ATTRIB)) goto cqe_seen; // - Try to open device - struct op *submitOp = mem_chunk_push(MemoryForDeviceOpenEvents); - submitOp->type = OP_DEVICE_OPEN; + struct op_device_open *submitOp = MemChunkPush(MemoryForDeviceOpenEvents); + debug_assert(submitOp); + *submitOp = stagedOp; struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); - io_uring_prep_openat(sqe, inputDirFd, path, O_RDONLY | O_NONBLOCK, 0); + io_uring_prep_openat(sqe, inputDirFd, (char *)submitOp->path.value, O_RDONLY | O_NONBLOCK, 0); io_uring_sqe_set_data(sqe, submitOp); io_uring_submit(&ring); } /* on device open events */ else if (op->type & OP_DEVICE_OPEN) { + struct op_device_open *op = io_uring_cqe_get_data(cqe); + // - Device open event is only one time, // so release used memory - mem_chunk_pop(MemoryForDeviceOpenEvents, op); + struct string path = op->path; + MemChunkPop(MemoryForDeviceOpenEvents, op); // - When open failed, exit immediately b8 isOpenAtFailed = cqe->res < 0; @@ -742,6 +781,7 @@ main(int argc, char *argv[]) int fd = cqe->res; struct op_joystick_poll stagedOp = { .type = OP_JOYSTICK_POLL, + .path = path, .fd = fd, }; @@ -801,7 +841,42 @@ main(int argc, char *argv[]) stagedOp.triggerRange = triggerAbsInfo.maximum - triggerAbsInfo.minimum; stagedOp.triggerMinimum = triggerAbsInfo.minimum; - printf("Input device ID: bus %#x vendor %#x product %#x\n", id.bustype, id.vendor, id.product); + { + struct string string; + u64 length = 0; + + string = STRING_FROM_ZERO_TERMINATED("Gamepad connected @ bus "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.bustype); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED(" vendor "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.vendor); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED(" product "); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, id.product); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = STRING_FROM_ZERO_TERMINATED("\n"); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + debug_assert(length <= stdoutBuffer.length); + write(STDOUT_FILENO, stdoutBuffer.value, length); + } + stagedOp.gamepad = GamepadGetNotConnected(gamepads, maxGamepadCount); if (!stagedOp.gamepad) { warning("Maximum number of gamepads connected! So not registering this one.\n"); @@ -814,7 +889,8 @@ main(int argc, char *argv[]) stagedOp.gamepad->isConnected = 1; // - Queue poll on gamepad for input event - struct op_joystick_poll *submitOp = mem_chunk_push(MemoryForJoystickPollEvents); + struct op_joystick_poll *submitOp = MemChunkPush(MemoryForJoystickPollEvents); + debug_assert(submitOp); *submitOp = stagedOp; struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); io_uring_prep_poll_add(sqe, submitOp->fd, POLLIN); @@ -847,12 +923,12 @@ main(int argc, char *argv[]) io_uring_sqe_set_data(sqe, 0); io_uring_submit(&ring); - // 2 - Release resources - mem_chunk_pop(MemoryForJoystickPollEvents, op); - - // 3 - Disconnect virtual gamepad + // 2 - Disconnect virtual gamepad op->gamepad->isConnected = 0; + // 3 - Release resources + MemChunkPop(MemoryForJoystickPollEvents, op); + // 4 - Stop trying to queue read joystick event goto cqe_seen; } @@ -864,7 +940,8 @@ main(int argc, char *argv[]) }; // - Acquire memory for read event - struct op_joystick_read *submitOp = mem_chunk_push(MemoryForJoystickReadEvents); + struct op_joystick_read *submitOp = MemChunkPush(MemoryForJoystickReadEvents); + debug_assert(submitOp); *submitOp = stagedOp; // - Queue read event @@ -893,14 +970,14 @@ main(int argc, char *argv[]) io_uring_sqe_set_data(sqe, 0); io_uring_submit(&ring); - // 2 - Release resources - mem_chunk_pop(MemoryForJoystickPollEvents, op_joystick_poll); - mem_chunk_pop(MemoryForJoystickReadEvents, op); - - // 4 - Disconnect virtual gamepad + // 2 - Disconnect virtual gamepad op_joystick_poll->gamepad->isConnected = 0; - // 3 - Stop polling on file descriptor + // 3 - Release resources + MemChunkPop(MemoryForJoystickPollEvents, op_joystick_poll); + MemChunkPop(MemoryForJoystickReadEvents, op); + + // 4 - Stop polling on file descriptor goto cqe_seen; } @@ -913,7 +990,7 @@ main(int argc, char *argv[]) io_uring_submit(&ring); // 2 - Release resources - mem_chunk_pop(MemoryForJoystickReadEvents, op); + MemChunkPop(MemoryForJoystickReadEvents, op); // 3 - Stop trying to read joystick another event goto cqe_seen; @@ -923,8 +1000,12 @@ main(int argc, char *argv[]) struct input_event *event = &op->event; #if GAMEPAD_IDLE_INHIBIT_DEBUG - printf("%p fd: %d time: %ld.%ld type: %d code: %d value: %d\n", op, op_joystick_poll->fd, event->input_event_sec, - event->input_event_usec, event->type, event->code, event->value); + // LEFTOFF: bug hunting + // 1 - pressing lt,rt on gamepad gives assertion error when normalizing event->value + // 2 - moving ls,rs on gamepad changes gamepad to invalid address SIGSEGV + + // printf("%p fd: %d time: %ldsec%ldus type: %d code: %d value: %d\n", op, op_joystick_poll->fd, + // event->input_event_sec, event->input_event_usec, event->type, event->code, event->value); #endif struct gamepad *gamepad = op_joystick_poll->gamepad; if (event->type == EV_SYN) { @@ -1028,24 +1109,89 @@ main(int argc, char *argv[]) } } - printf("Gamepad #%p a: %d b: %d x: %d y: %d ls: %d %.2f,%.2f rs: %d %.2f,%.2f lb: %d lt: %.2f rb: %d rt: %.2f " - "home: %d back: %d start: %d\n", - // pointer - gamepad, - // buttons - gamepad->a, gamepad->b, gamepad->x, gamepad->y, - // left stick - gamepad->ls, gamepad->lsX, gamepad->lsY, - // right stick - gamepad->rs, gamepad->rsX, gamepad->rsY, - // left button, left trigger - gamepad->lb, gamepad->lt, - // right button, right trigger - gamepad->rb, gamepad->rt, - // buttons - gamepad->home, gamepad->back, gamepad->start); + struct string string; + u64 length = 0; + + string = STRING_FROM_ZERO_TERMINATED("Gamepad #"); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + string = FormatHex(&stringBuffer, (u64)&gamepad); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + +#define PRINT_BUTTON(prefix, button) \ + string = STRING_FROM_ZERO_TERMINATED(" " prefix ": "); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatU64(&stringBuffer, gamepad->button); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + +#define PRINT_ANALOG(prefix, stick, stickX, stickY) \ + string = STRING_FROM_ZERO_TERMINATED(" " prefix ": "); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatU64(&stringBuffer, gamepad->stick); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = STRING_FROM_ZERO_TERMINATED(" "); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatF32(&stringBuffer, gamepad->stickX, 2); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = STRING_FROM_ZERO_TERMINATED(","); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatF32(&stringBuffer, gamepad->stickY, 2); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + +#define PRINT_TRIGGER(prefix, trigger) \ + string = STRING_FROM_ZERO_TERMINATED(" " prefix ": "); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length; \ + string = FormatF32(&stringBuffer, gamepad->trigger, 2); \ + memcpy(stdoutBuffer.value + length, string.value, string.length); \ + length += string.length + + PRINT_BUTTON("a", a); + PRINT_BUTTON("b", b); + PRINT_BUTTON("x", x); + PRINT_BUTTON("y", y); + + PRINT_ANALOG("ls", ls, lsX, lsY); + PRINT_ANALOG("rs", rs, rsX, rsY); + + PRINT_BUTTON("lb", lb); + PRINT_TRIGGER("lt", lt); + + PRINT_BUTTON("rb", rb); + PRINT_TRIGGER("rt", rt); + + PRINT_BUTTON("home", home); + PRINT_BUTTON("back", back); + PRINT_BUTTON("start", start); + + string = STRING_FROM_ZERO_TERMINATED("\n"); + memcpy(stdoutBuffer.value + length, string.value, string.length); + length += string.length; + + debug_assert(length <= stdoutBuffer.length); + write(STDOUT_FILENO, stdoutBuffer.value, length); + +#undef PRINT_BUTTON +#undef PRINT_ANALOG +#undef PRINT_TRIGGER } + // DEBUG: get file path from file descriptor + // char path[256]; + // snprintf(path, sizeof(path), "/proc/self/fd/%d", op_joystick_poll->fd); + // readlink(path, path, sizeof(path)); + // printf("fd: %d path: %s\n", op_joystick_poll->fd, path); + // NOTE: When there is more data to read // 1 - Try to read another joystick event struct op_joystick_read *submitOp = op; diff --git a/src/text.h b/src/text.h deleted file mode 100644 index 1d3bf26..0000000 --- a/src/text.h +++ /dev/null @@ -1,325 +0,0 @@ -#ifndef __TEXT_H__ -#define __TEXT_H__ - -struct string { - u8 *data; - u64 len; -}; - -static inline struct string -StringFromZeroTerminated(u8 *string) -{ - debug_assert(string != 0); - u64 len = strnlen((char *)string, 1024); - return (struct string){ - .data = string, - .len = len, - }; -} - -static inline b8 -IsStringEqual(struct string *left, struct string *right) -{ - if (!left || !right || left->len != right->len) - return 0; - - for (u64 index = 0; index < left->len; index++) { - if (left->data[index] != right->data[index]) - return 0; - } - - return 1; -} - -static inline b8 -IsStringContains(struct string *string, struct string *search) -{ - if (!string || !search || string->len < search->len) - return 0; - - /* TEST CODE - struct string string; - struct string search; - b8 result; - - string = (struct string){.data = (u8 *)"abc def ghi", .len = 11}; - search = (struct string){.data = (u8 *)"abc", .len = 3}; - debug_assert(IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"def", .len = 3}; - debug_assert(IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"ghi", .len = 3}; - debug_assert(IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"ghijkl", .len = 6}; - debug_assert(!IsStringContains(&string, &search)); - - search = (struct string){.data = (u8 *)"jkl", .len = 3}; - debug_assert(!IsStringContains(&string, &search)); - */ - - for (u64 stringIndex = 0; stringIndex < string->len; stringIndex++) { - b8 isFound = 1; - for (u64 searchIndex = 0, substringIndex = stringIndex; searchIndex < search->len; - searchIndex++, substringIndex++) { - b8 isEndOfString = substringIndex == string->len; - if (isEndOfString) { - isFound = 0; - break; - } - - b8 isCharactersNotMatching = string->data[substringIndex] != search->data[searchIndex]; - if (isCharactersNotMatching) { - isFound = 0; - break; - } - } - - if (isFound) - return 1; - } - - return 0; -} - -static inline b8 -IsStringStartsWith(struct string *string, struct string *search) -{ - if (!string || !search || string->len < search->len) - return 0; - - /* TEST CODE - struct string string; - struct string search; - - string = (struct string){.data = (u8 *)"abc def ghi", .len = 11}; - search = (struct string){.data = (u8 *)"abc", .len = 3}; - debug_assert(IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"def", .len = 3}; - debug_assert(!IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"ghi", .len = 3}; - debug_assert(!IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"ghijkl", .len = 6}; - debug_assert(!IsStringStartsWith(&string, &search)); - - search = (struct string){.data = (u8 *)"jkl", .len = 3}; - debug_assert(!IsStringStartsWith(&string, &search)); - */ - - for (u64 searchIndex = 0; searchIndex < search->len; searchIndex++) { - b8 isCharactersNotMatching = string->data[searchIndex] != search->data[searchIndex]; - if (isCharactersNotMatching) - return 0; - } - - return 1; -} - -struct duration { - u64 ns; -}; - -static inline b8 -ParseDuration(struct string *string, struct duration *duration) -{ - if (!string || string->len == 0 || string->len < 3) - return 0; - - /* TEST CODE - struct string string; - struct duration duration; - b8 isParsingSuccessful; - - string = (struct string){.data = (u8 *)"1ns", .len = 3}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 1); - - string = (struct string){.data = (u8 *)"1sec", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 1 * 1e9L); - - string = (struct string){.data = (u8 *)"5sec", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L); - - string = (struct string){.data = (u8 *)"5min", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L * 60); - - string = (struct string){.data = (u8 *)"5day", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == 5 * 1e9L * 60 * 60 * 24); - - string = (struct string){.data = (u8 *)"1hr5min", .len = 7}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(isParsingSuccessful && duration.ns == (1 * 1e9L * 60 * 60) + (5 * 1e9L * 60)); - - string = (struct string){.data = (u8 *)0, .len = 0}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)"", .len = 0}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)" ", .len = 1}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)"abc", .len = 3}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful); - - string = (struct string){.data = (u8 *)"5m5s", .len = 4}; - isParsingSuccessful = ParseDuration(&string, &duration); - debug_assert(!isParsingSuccessful) - */ - - // | Duration | Length | - // |----------|-------------| - // | ns | nanosecond | - // | us | microsecond | - // | ms | millisecond | - // | sec | second | - // | min | minute | - // | hr | hour | - // | day | day | - // | wk | week | - -#define UNIT_STRING(variableName, zeroTerminatedString) \ - static struct string variableName = { \ - .data = (u8 *)zeroTerminatedString, \ - .len = sizeof(zeroTerminatedString) - 1, \ - } - UNIT_STRING(nanosecondUnitString, "ns"); - UNIT_STRING(microsecondUnitString, "us"); - UNIT_STRING(millisocondUnitString, "ms"); - UNIT_STRING(secondUnitString, "sec"); - UNIT_STRING(minuteUnitString, "min"); - UNIT_STRING(hourUnitString, "hr"); - UNIT_STRING(dayUnitString, "day"); - UNIT_STRING(weekUnitString, "wk"); -#undef UNIT_STRING - - b8 isUnitExistsInString = - IsStringContains(string, &nanosecondUnitString) || IsStringContains(string, µsecondUnitString) || - IsStringContains(string, &millisocondUnitString) || IsStringContains(string, &secondUnitString) || - IsStringContains(string, &minuteUnitString) || IsStringContains(string, &hourUnitString) || - IsStringContains(string, &dayUnitString) || IsStringContains(string, &weekUnitString); - if (!isUnitExistsInString) { - return 0; - } - - struct duration parsed = {}; - u64 value = 0; - for (u64 index = 0; index < string->len; index++) { - u8 digitCharacter = string->data[index]; - b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; - if (!isDigit) { - // - get unit - struct string unitString = {.data = string->data + index, .len = string->len - index}; - if (/* unit: nanosecond */ IsStringStartsWith(&unitString, &nanosecondUnitString)) { - parsed.ns += value; - index += nanosecondUnitString.len; - } else if (/* unit: microsecond */ IsStringStartsWith(&unitString, µsecondUnitString)) { - parsed.ns += value * 1e3L; - index += microsecondUnitString.len; - } else if (/* unit: millisecond */ IsStringStartsWith(&unitString, &millisocondUnitString)) { - parsed.ns += value * 1e6L; - index += millisocondUnitString.len; - } else if (/* unit: second */ IsStringStartsWith(&unitString, &secondUnitString)) { - parsed.ns += value * 1e9L; - index += secondUnitString.len; - } else if (/* unit: minute */ IsStringStartsWith(&unitString, &minuteUnitString)) { - parsed.ns += value * 1e9L * 60; - index += minuteUnitString.len; - } else if (/* unit: hour */ IsStringStartsWith(&unitString, &hourUnitString)) { - parsed.ns += value * 1e9L * 60 * 60; - index += hourUnitString.len; - } else if (/* unit: day */ IsStringStartsWith(&unitString, &dayUnitString)) { - parsed.ns += value * 1e9L * 60 * 60 * 24; - index += dayUnitString.len; - } else if (/* unit: week */ IsStringStartsWith(&unitString, &weekUnitString)) { - parsed.ns += value * 1e9L * 60 * 60 * 24 * 7; - index += weekUnitString.len; - } else { - // unsupported unit - return 0; - } - - // - reset value - value = 0; - continue; - } - - value *= 10; - u8 digit = digitCharacter - (u8)'0'; - value += digit; - } - - *duration = parsed; - - return 1; -} - -/* TEST CODE - struct duration left; - struct duration right; - - left = (struct duration){.ns = 1 * 1e9L}; - right = (struct duration){.ns = 5 * 1e9L}; - debug_assert(IsDurationLessThen(&left, &right)); - debug_assert(!IsDurationGraterThan(&left, &right)); - - left = (struct duration){.ns = 1 * 1e9L}; - right = (struct duration){.ns = 1 * 1e9L}; - debug_assert(!IsDurationLessThan(&left, &right)); - debug_assert(!IsDurationGraterThan(&left, &right)); - - left = (struct duration){.ns = 5 * 1e9L}; - right = (struct duration){.ns = 1 * 1e9L}; - debug_assert(!IsDurationLessThan(&left, &right)); - debug_assert(IsDurationGraterThan(&left, &right)); - - return 0; - */ -static inline b8 -IsDurationLessThan(struct duration *left, struct duration *right) -{ - return left->ns < right->ns; -} - -static inline b8 -IsDurationGraterThan(struct duration *left, struct duration *right) -{ - return left->ns > right->ns; -} - -static inline b8 -ParseU64(struct string *string, u64 *value) -{ - if (!string) - return 0; - - u64 parsed = 0; - for (u64 index = 0; index < string->len; index++) { - u8 digitCharacter = string->data[index]; - b8 isDigit = digitCharacter >= '0' && digitCharacter <= '9'; - if (!isDigit) { - return 1; - } - - parsed *= 10; - u8 digit = digitCharacter - (u8)'0'; - parsed += digit; - } - - *value = parsed; - return 1; -} - -#endif /* __TEXT_H__ */ diff --git a/test/memory_test.c b/test/memory_test.c new file mode 100644 index 0000000..b52860a --- /dev/null +++ b/test/memory_test.c @@ -0,0 +1,137 @@ +#include "memory.h" + +// TODO: Show error pretty error message when a test fails +enum memory_test_error { + MEMORY_TEST_ERROR_NONE = 0, + MEMORY_TEST_ERROR_MEM_PUSH_EXPECTED_VALID_ADDRESS_1, + MEMORY_TEST_ERROR_MEM_PUSH_EXPECTED_VALID_ADDRESS_2, + MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_VALID_ADDRESS_1, + MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_VALID_ADDRESS_2, + MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_VALID_ADDRESS_3, + MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_NULL, + MEMORY_TEST_ERROR_MEM_CHUNK_POP_EXPECTED_SAME_ADDRESS, + + // src: https://mesonbuild.com/Unit-tests.html#skipped-tests-and-hard-errors + // For the default exitcode testing protocol, the GNU standard approach in this case is to exit the program with error + // code 77. Meson will detect this and report these tests as skipped rather than failed. This behavior was added in + // version 0.37.0. + MESON_TEST_SKIP = 77, + // In addition, sometimes a test fails set up so that it should fail even if it is marked as an expected failure. The + // GNU standard approach in this case is to exit the program with error code 99. Again, Meson will detect this and + // report these tests as ERROR, ignoring the setting of should_fail. This behavior was added in version 0.50.0. + MESON_TEST_FAILED_TO_SET_UP = 99, +}; + +int +main(void) +{ + enum memory_test_error errorCode = MEMORY_TEST_ERROR_NONE; + struct memory_block memory; + struct memory_temp tempMemory; + + { + u64 KILOBYTES = 1 << 10; + u64 total = 1 * KILOBYTES; + memory = (struct memory_block){.block = alloca(total), .total = total}; + if (memory.block == 0) { + errorCode = MESON_TEST_FAILED_TO_SET_UP; + goto end; + } + bzero(memory.block, memory.total); + } + + // MemPush(struct memory_block *mem, u64 size) + tempMemory = MemTempBegin(&memory); + { + void *expected; + void *value; + + expected = memory.block; + value = MemPush(&memory, 16); + if (value != expected) { + errorCode = MEMORY_TEST_ERROR_MEM_PUSH_EXPECTED_VALID_ADDRESS_1; + goto end; + } + + expected = memory.block + 16; + value = MemPush(&memory, 16); + if (value != expected) { + errorCode = MEMORY_TEST_ERROR_MEM_PUSH_EXPECTED_VALID_ADDRESS_2; + goto end; + } + } + MemTempEnd(&tempMemory); + + // MemChunkPush(struct memory_chunk *chunk) + tempMemory = MemTempBegin(&memory); + { + struct memory_chunk *chunk = MemPushChunk(&memory, 16, 3); + void *expected; + void *value; + u64 index; + u8 flag; + + /* + * | memory_chunk | + * |--------------------------------------------| + * | flags -> max*sizeof(u8) | data -> max*size | + */ + void *flagStartAddress = (u8 *)chunk->block; + void *dataStartAddress = (u8 *)chunk->block + (sizeof(u8) * chunk->max); + + index = 0; + expected = dataStartAddress + (16 * index); + value = MemChunkPush(chunk); + flag = *((u8 *)flagStartAddress + index); + if (value != expected || flag != 1) { + errorCode = MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_VALID_ADDRESS_1; + goto end; + } + + index = 1; + expected = dataStartAddress + (16 * index); + value = MemChunkPush(chunk); + flag = *((u8 *)flagStartAddress + index); + if (value != expected || flag != 1) { + errorCode = MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_VALID_ADDRESS_2; + goto end; + } + + index = 2; + expected = dataStartAddress + (16 * index); + value = MemChunkPush(chunk); + flag = *((u8 *)flagStartAddress + index); + if (value != expected || flag != 1) { + errorCode = MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_VALID_ADDRESS_3; + goto end; + } + + expected = 0; + value = MemChunkPush(chunk); + if (value != expected) { + errorCode = MEMORY_TEST_ERROR_MEM_CHUNK_PUSH_EXPECTED_NULL; + goto end; + } + } + MemTempEnd(&tempMemory); + + // MemChunkPop(struct memory_chunk *chunk, void *block) + tempMemory = MemTempBegin(&memory); + { + struct memory_chunk *chunk = MemPushChunk(&memory, 16, 3); + void *expected; + void *value; + + expected = MemChunkPush(chunk); + MemChunkPop(chunk, expected); + value = MemChunkPush(chunk); + if (value != expected) { + errorCode = MEMORY_TEST_ERROR_MEM_CHUNK_POP_EXPECTED_SAME_ADDRESS; + goto end; + } + } + MemTempEnd(&tempMemory); + +end: + return (int)errorCode; +} diff --git a/test/meson.build b/test/meson.build new file mode 100644 index 0000000..fef9e7e --- /dev/null +++ b/test/meson.build @@ -0,0 +1,5 @@ +t = executable('text_test', 'text_test.c', include_directories: '../include') +test('text', t) + +t = executable('memory_test', 'memory_test.c', include_directories: '../include') +test('memory', t) diff --git a/test/text_test.c b/test/text_test.c new file mode 100644 index 0000000..59cf315 --- /dev/null +++ b/test/text_test.c @@ -0,0 +1,497 @@ +#include "text.h" + +// TODO: Show error pretty error message when a test fails +enum text_test_error { + TEXT_TEST_ERROR_NONE = 0, + TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED, + TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED_TRUNCATED, + TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_TRUE, + TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_FALSE, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_1, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_2, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_3, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_1, + TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_2, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_TRUE, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_1, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_2, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_3, + TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_4, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1NS, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1SEC, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_5SEC, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_7MIN, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1HR5MIN, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_10DAY, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_10DAY1SEC, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NULL, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_EMPTY, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_SPACE, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NO_DURATION_STRING, + TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_WRONG_DURATION_NAMES, + TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_TRUE, + TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_FALSE, + TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_TRUE, + TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_FALSE, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_0, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_1, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_3912, + TEXT_TEST_ERROR_FORMATU64_EXPECTED_18446744073709551615, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_0_9, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_1_0, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_1_00, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_50, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_55, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_0_9, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_1_0, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_1_00, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_50, + TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_55, + + // src: https://mesonbuild.com/Unit-tests.html#skipped-tests-and-hard-errors + // For the default exitcode testing protocol, the GNU standard approach in this case is to exit the program with error + // code 77. Meson will detect this and report these tests as skipped rather than failed. This behavior was added in + // version 0.37.0. + MESON_TEST_SKIP = 77, + // In addition, sometimes a test fails set up so that it should fail even if it is marked as an expected failure. The + // GNU standard approach in this case is to exit the program with error code 99. Again, Meson will detect this and + // report these tests as ERROR, ignoring the setting of should_fail. This behavior was added in version 0.50.0. + MESON_TEST_FAILED_TO_SET_UP = 99, +}; + +int +main(void) +{ + enum text_test_error errorCode = TEXT_TEST_ERROR_NONE; + + // StringFromZeroTerminated + { + char *input = "abc"; + struct string result = StringFromZeroTerminated((u8 *)input, 1024); + + char *expectedValue = input; + u64 expectedLength = 3; + if ((char *)result.value != expectedValue || result.length != expectedLength) { + errorCode = TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED; + goto end; + } + } + + { + char *input = "abcdefghijklm"; + struct string result = StringFromZeroTerminated((u8 *)input, 3); + + char *expectedValue = input; + u64 expectedLength = 3; + if (result.length != expectedLength || result.value != (u8 *)expectedValue) { + errorCode = TEXT_TEST_ERROR_STRING_FROM_ZERO_TERMINATED_TRUNCATED; + goto end; + } + } + + // IsStringEqual(struct string *left, struct string *right) + { + struct string leftString; + struct string rightString; + b8 expected; + + leftString = (struct string){.value = (u8 *)"abc", .length = 3}; + rightString = (struct string){.value = (u8 *)"abc", .length = 3}; + expected = 1; + if (IsStringEqual(&leftString, &rightString) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_TRUE; + goto end; + } + + rightString = (struct string){.value = (u8 *)"abc def ghi", .length = 11}; + expected = 0; + if (IsStringEqual(&leftString, &rightString) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_EQUAL_MUST_BE_FALSE; + goto end; + } + } + + // IsStringContains(struct string *string, struct string *search) + { + struct string string; + struct string search; + b8 expected; + + string = (struct string){.value = (u8 *)"abc def ghi", .length = 11}; + search = (struct string){.value = (u8 *)"abc", .length = 3}; + expected = 1; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_1; + goto end; + } + + search = (struct string){.value = (u8 *)"def", .length = 3}; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_2; + goto end; + } + + search = (struct string){.value = (u8 *)"ghi", .length = 3}; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_TRUE_3; + goto end; + } + + search = (struct string){.value = (u8 *)"ghijkl", .length = 6}; + expected = 0; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_1; + goto end; + } + + search = (struct string){.value = (u8 *)"jkl", .length = 3}; + if (IsStringContains(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_CONTAINS_EXPECTED_FALSE_2; + goto end; + } + } + + // IsStringStartsWith(struct string *string, struct string *search) + { + struct string string; + struct string search; + b8 expected; + + string = (struct string){.value = (u8 *)"abc def ghi", .length = 11}; + search = (struct string){.value = (u8 *)"abc", .length = 3}; + expected = 1; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_TRUE; + goto end; + } + + search = (struct string){.value = (u8 *)"def", .length = 3}; + expected = 0; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_1; + goto end; + } + + search = (struct string){.value = (u8 *)"ghi", .length = 3}; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_2; + goto end; + } + + search = (struct string){.value = (u8 *)"ghijkl", .length = 6}; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_3; + goto end; + } + + search = (struct string){.value = (u8 *)"jkl", .length = 3}; + if (IsStringStartsWith(&string, &search) != expected) { + errorCode = TEXT_TEST_ERROR_IS_STRING_STARTS_WITH_EXPECTED_FALSE_4; + goto end; + } + } + + // ParseDuration(struct string *string, struct duration *duration) + { + struct string string; + struct duration duration; + b8 value; + b8 expected; + u64 expectedDurationInNs; + + string = (struct string){.value = (u8 *)"1ns", .length = 3}; + expected = 1; + expectedDurationInNs = 1; + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1NS; + goto end; + } + + string = (struct string){.value = (u8 *)"1sec", .length = 4}; + expected = 1; + expectedDurationInNs = 1 * 1e9L; + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1SEC; + goto end; + } + + string = (struct string){.value = (u8 *)"5sec", .length = 4}; + expectedDurationInNs = 5 * 1e9L; + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_5SEC; + goto end; + } + + string = (struct string){.value = (u8 *)"7min", .length = 4}; + expectedDurationInNs = 7 * 1e9L * 60; + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_7MIN; + goto end; + } + + string = (struct string){.value = (u8 *)"1hr5min", .length = 7}; + expectedDurationInNs = (1 * 1e9L * 60 * 60) + (5 * 1e9L * 60); + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_1HR5MIN; + goto end; + } + + string = (struct string){.value = (u8 *)"10day", .length = 5}; + expectedDurationInNs = 10 * 1e9L * 60 * 60 * 24; + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_10DAY; + goto end; + } + + string = (struct string){.value = (u8 *)"10day1sec", .length = 9}; + expectedDurationInNs = (10 * 1e9L * 60 * 60 * 24) + (1 * 1e9L); + value = ParseDuration(&string, &duration); + if (value != expected || duration.ns != expectedDurationInNs) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_TRUE_10DAY1SEC; + goto end; + } + + string = (struct string){.value = (u8 *)0, .length = 0}; + expected = 0; + value = ParseDuration(&string, &duration); + if (value != expected) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NULL; + goto end; + } + + string = (struct string){.value = (u8 *)"", .length = 0}; + value = ParseDuration(&string, &duration); + if (value != expected) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_EMPTY; + goto end; + } + + string = (struct string){.value = (u8 *)" ", .length = 1}; + value = ParseDuration(&string, &duration); + if (value != expected) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_SPACE; + goto end; + } + + string = (struct string){.value = (u8 *)"abc", .length = 3}; + value = ParseDuration(&string, &duration); + if (value != expected) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_NO_DURATION_STRING; + goto end; + } + + string = (struct string){.value = (u8 *)"5m5s", .length = 4}; + value = ParseDuration(&string, &duration); + if (value != expected) { + errorCode = TEXT_TEST_ERROR_PARSE_DURATION_EXPECTED_FALSE_WRONG_DURATION_NAMES; + goto end; + } + } + + // IsDurationLessThan(struct duration *left, struct duration *right) + // IsDurationGraterThan(struct duration *left, struct duration *right) + { + struct duration left; + struct duration right; + b8 expectedLessThan; + b8 expectedGraterThan; + + left = (struct duration){.ns = 1 * 1e9L}; + right = (struct duration){.ns = 5 * 1e9L}; + expectedLessThan = 1; + if (IsDurationLessThan(&left, &right) != expectedLessThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_TRUE; + goto end; + } + expectedGraterThan = 0; + if (IsDurationGraterThan(&left, &right) != expectedGraterThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_FALSE; + goto end; + } + + left = (struct duration){.ns = 1 * 1e9L}; + right = (struct duration){.ns = 1 * 1e9L}; + expectedLessThan = 0; + if (IsDurationLessThan(&left, &right) != expectedLessThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_FALSE; + goto end; + } + expectedGraterThan = 0; + if (IsDurationGraterThan(&left, &right) != expectedGraterThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_FALSE; + goto end; + } + + left = (struct duration){.ns = 5 * 1e9L}; + right = (struct duration){.ns = 1 * 1e9L}; + expectedLessThan = 0; + if (IsDurationLessThan(&left, &right) != expectedLessThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_LESS_THAN_EXPECTED_FALSE; + goto end; + } + expectedGraterThan = 1; + if (IsDurationGraterThan(&left, &right) != expectedGraterThan) { + errorCode = TEXT_TEST_ERROR_IS_DURATION_GRATER_THAN_EXPECTED_TRUE; + goto end; + } + } + + // FormatU64(struct string *stringBuffer, u64 value) + { + u8 buf[20]; + struct string stringBuffer = {.value = buf, .length = sizeof(buf)}; + struct string expected; + struct string value; + + value = FormatU64(&stringBuffer, 0); + expected = (struct string){.value = (u8 *)"0", .length = 1}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_0; + goto end; + } + + value = FormatU64(&stringBuffer, 1); + expected = (struct string){.value = (u8 *)"1", .length = 1}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_0; + goto end; + } + + value = FormatU64(&stringBuffer, 3912); + expected = (struct string){.value = (u8 *)"3912", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_3912; + goto end; + } + + value = FormatU64(&stringBuffer, 18446744073709551615UL); + expected = (struct string){.value = (u8 *)"18446744073709551615", .length = 20}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATU64_EXPECTED_18446744073709551615; + goto end; + } + } + + // FormatF32(struct string *stringBuffer, f32 value, u32 fractionCount) + { + u8 buf[20]; + struct string stringBuffer = {.value = buf, .length = sizeof(buf)}; + struct string expected; + struct string value; + + value = FormatF32(&stringBuffer, 0.99f, 1); + expected = (struct string){.value = (u8 *)"0.9", .length = 3}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_0_9; + goto end; + } + + value = FormatF32(&stringBuffer, 1.0f, 1); + expected = (struct string){.value = (u8 *)"1.0", .length = 3}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_1_0; + goto end; + } + + value = FormatF32(&stringBuffer, 1.0f, 2); + expected = (struct string){.value = (u8 *)"1.00", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_1_00; + goto end; + } + + value = FormatF32(&stringBuffer, 2.50f, 2); + expected = (struct string){.value = (u8 *)"2.50", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_50; + goto end; + } + + value = FormatF32(&stringBuffer, 2.55999f, 2); + expected = (struct string){.value = (u8 *)"2.55", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_2_55; + goto end; + } + + value = FormatF32(&stringBuffer, -0.99f, 1); + expected = (struct string){.value = (u8 *)"-0.9", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_0_9; + goto end; + } + + value = FormatF32(&stringBuffer, -1.0f, 1); + expected = (struct string){.value = (u8 *)"-1.0", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_1_0; + goto end; + } + + value = FormatF32(&stringBuffer, -1.0f, 2); + expected = (struct string){.value = (u8 *)"-1.00", .length = 5}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_1_00; + goto end; + } + + value = FormatF32(&stringBuffer, -2.50f, 2); + expected = (struct string){.value = (u8 *)"-2.50", .length = 5}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_50; + goto end; + } + + value = FormatF32(&stringBuffer, -2.55999f, 2); + expected = (struct string){.value = (u8 *)"-2.55", .length = 5}; + if (!IsStringEqual(&value, &expected)) { + errorCode = TEXT_TEST_ERROR_FORMATF32_EXPECTED_NEGATIVE_2_55; + goto end; + } + } + + // FormatHex(struct string *stringBuffer, u64 value) + { + u8 buf[18]; + struct string stringBuffer = {.value = buf, .length = sizeof(buf)}; + struct string expected; + struct string value; + + value = FormatHex(&stringBuffer, 0x0); + expected = (struct string){.value = (u8 *)"0x00", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + + value = FormatHex(&stringBuffer, 0x4); + expected = (struct string){.value = (u8 *)"0x04", .length = 4}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + + value = FormatHex(&stringBuffer, 0x00f2aa499b9028eaul); + expected = (struct string){.value = (u8 *)"0x00f2aa499b9028ea", .length = 18}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + + value = FormatHex(&stringBuffer, 0x00f2aa499b9028eaul); + expected = (struct string){.value = (u8 *)"0x00f2aa499b9028ea", .length = 18}; + if (!IsStringEqual(&value, &expected)) { + errorCode = -1; + goto end; + } + } + +end: + return (int)errorCode; +}