497 lines
13 KiB
C
497 lines
13 KiB
C
#ifndef RT_CORE_H
|
|
#define RT_CORE_H
|
|
|
|
/*
|
|
* rtcore.h - Simple base layer library for C
|
|
* Copyright (C) 2025 Kevin Trogant
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
/* My base layer library.
|
|
* - fixed width types
|
|
* - arena allocator
|
|
* - utility macros
|
|
* - string type
|
|
* - simd header inclusion
|
|
* - threading wrapper
|
|
* - atomics
|
|
* - simple file io
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <uchar.h>
|
|
#include <string.h>
|
|
|
|
#ifndef RTC_API
|
|
#ifdef __cplusplus
|
|
#define RTC_API extern "C"
|
|
#else
|
|
#define RTC_API extern
|
|
#endif
|
|
#endif
|
|
|
|
/* Nowadays, immintrin.h includes everything I tend to care about (simd)
|
|
* This assume x64, which is what I'm always using.
|
|
* You still need to build with the appropriate compilation
|
|
* flags (-march=avx2 etc. for gcc, /arch:AVX2 etc. for msvc) */
|
|
#include <immintrin.h>
|
|
/* MSVC additionally has intrin.h for microsoft specific intrinsics */
|
|
#ifdef _MSC_VER
|
|
#include <intrin.h>
|
|
#endif
|
|
|
|
/* Types */
|
|
typedef uint8_t u8;
|
|
typedef uint16_t u16;
|
|
typedef uint32_t u32;
|
|
typedef uint64_t u64;
|
|
typedef int8_t i8;
|
|
typedef int16_t i16;
|
|
typedef int32_t i32;
|
|
typedef int64_t i64;
|
|
typedef float f32;
|
|
typedef double f64;
|
|
typedef ptrdiff_t isize;
|
|
typedef size_t usize;
|
|
typedef char byte;
|
|
typedef char16_t c16;
|
|
typedef int32_t b32;
|
|
|
|
/* Utility macros */
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#ifdef __has_builtin
|
|
#if __has_builtin(__builtin_expect)
|
|
#define likely(x) __builtin_expect(!!(x), 1)
|
|
#define unlikely(x) __builtin_expect(!!(x), 0)
|
|
#endif
|
|
#endif
|
|
#endif
|
|
#ifndef likely
|
|
#define likely(x) (x)
|
|
#define unlikely(x) (x)
|
|
#endif
|
|
|
|
#define internal static
|
|
#define global_variable static
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define exported __attribute__((visibility("default")))
|
|
#elif defined(_MSC_VER)
|
|
#define exported __dllexport
|
|
#endif
|
|
|
|
#define min(a, b) ((a) < (b) ? (a) : (b))
|
|
#define max(a, b) ((a) > (b) ? (a) : (b))
|
|
|
|
#define kilobytes(n) ((n) * 1024)
|
|
#define megabytes(n) ((n) * 1024 * 1024)
|
|
#define gigabytes(n) ((n) * 1024 * 1024 * 1024)
|
|
|
|
#define isizeof(x) (isize)sizeof(x)
|
|
/* number of elements in array a */
|
|
#define countof(a) (isize)(sizeof(a) / sizeof(*(a)))
|
|
/* number of characters in string constant s (exluding the terminating 0) */
|
|
#define lengthof(s) (countof(s) - 1)
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
#define assert(x) \
|
|
while (!(x)) \
|
|
{ \
|
|
__builtin_trap(); \
|
|
__builtin_unreachable(); \
|
|
}
|
|
|
|
#define force_inline inline __attribute__((always_inline))
|
|
#elif defined(_MSC_VER)
|
|
#define assert(x) \
|
|
if (!(x)) \
|
|
{ \
|
|
__debugbreak(); \
|
|
}
|
|
|
|
#define force_inline __forceinline
|
|
#endif
|
|
|
|
/* Arena allocator */
|
|
typedef struct arena
|
|
{
|
|
byte *begin;
|
|
byte *end;
|
|
} arena;
|
|
|
|
typedef struct arena_cp
|
|
{
|
|
byte *cp;
|
|
} arena_cp;
|
|
|
|
static force_inline arena_cp SaveArena(arena a)
|
|
{
|
|
return (arena_cp){a.begin};
|
|
}
|
|
|
|
static force_inline void RestoreArena(arena *a, arena_cp cp)
|
|
{
|
|
a->begin = cp.cp;
|
|
}
|
|
|
|
enum
|
|
{
|
|
/* Disables memory zeroing */
|
|
ALLOC_NOZERO = 0x1,
|
|
/* Allocation failure returns NULL instead of aborting */
|
|
ALLOC_SOFTFAIL = 0x2,
|
|
};
|
|
|
|
/* allocate objects from an arena.
|
|
* usage is one of:
|
|
* - a(a, t) - allocate one object of type t from arena a
|
|
* - alloc(a, t, n) - allocate n objects of type t from arena a
|
|
* - (a, t, n, f) - allocate n objects of type t from arena a using flags f
|
|
*/
|
|
#define alloc(...) allocx(__VA_ARGS__, alloc4, alloc3, alloc2)(__VA_ARGS__)
|
|
#define allocx(a, b, c, d, e, ...) e
|
|
#define alloc2(a, t) (t*)ArenaAlloc(a, isizeof(t), _Alignof(t), 1, 0)
|
|
#define alloc3(a, t, n) (t*)ArenaAlloc(a, isizeof(t), _Alignof(t), n, 0)
|
|
#define alloc4(a, t, n, f) (t*)ArenaAlloc(a, isizeof(t), _Alignof(t), n, f)
|
|
|
|
RTC_API void *ArenaAlloc(arena *a, isize size, isize align, isize n, int flags);
|
|
|
|
/* String type */
|
|
typedef struct s8
|
|
{
|
|
u8 *data;
|
|
isize length;
|
|
} s8;
|
|
#define S8(_s) \
|
|
(s8) { .data = (u8 *)_s, .length = lengthof(_s), }
|
|
|
|
typedef struct { s8 first; s8 second; } split_result;
|
|
|
|
/* constructs a string containing the bytes between begin and end (exclusive) */
|
|
RTC_API s8 S8Span(u8* begin, u8* end);
|
|
/* check if two strings are equal */
|
|
RTC_API b32 S8Equals(s8 a, s8 b);
|
|
/* compare two strings. analogue to strcmp */
|
|
RTC_API isize S8Compare(s8 a, s8 b);
|
|
/* compute a simple hash value for a string. */
|
|
RTC_API u64 S8Hash(s8 s);
|
|
/* returns s with leading and trailing whitespace removed.
|
|
* Treats all characters with a numerical value smaller and equal to 0x20 (space) as whitespace
|
|
* The new string shares memory with s */
|
|
RTC_API s8 S8Trim(s8 s);
|
|
/* returns s with leading whitespace removed.
|
|
* Treats all characters with a numerical value smaller and equal to 0x20 (space) as whitespace.
|
|
* The new string shares memory with s */
|
|
RTC_API s8 S8TrimLeft(s8 s);
|
|
/* returns s with trailing whitespace removed.
|
|
* Treats all characters with a numerical value smaller and equal to 0x20 (space) as whitespace.
|
|
* The new string shares memory with s */
|
|
RTC_API s8 S8TrimRight(s8 s);
|
|
/* Returns a pointer to the first occurence of character c (or NULL) */
|
|
RTC_API u8 *S8Chr(s8 s, u8 c);
|
|
/* Returns a pointer to the last valid character of s */
|
|
RTC_API u8 *S8End(s8 s);
|
|
/* Splits the given string s at the first occurence of c.
|
|
* If c does not occur, split_result.first contains the whole string */
|
|
RTC_API split_result S8Split(s8 s, u8 c);
|
|
/* Creates a clone of string s on arena a */
|
|
RTC_API s8 S8Clone(s8 s, arena *a);
|
|
|
|
/* Basic file io */
|
|
typedef struct file_buffer
|
|
{
|
|
byte *data;
|
|
isize length;
|
|
} file_buffer;
|
|
/* Loads the content of a file. */
|
|
RTC_API file_buffer ReadEntireFile(s8 path, arena *a);
|
|
/* Loads the content of a file into a s8 */
|
|
RTC_API s8 ReadEntireFileS8(s8 path, arena *a);
|
|
/* Write the given buffer to a file */
|
|
RTC_API b32 WriteEntireFile(s8 path, byte *data, isize length);
|
|
|
|
/* Atomics */
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
/* Atomic add */
|
|
#define AtomicAdd32(_addend, _val) __atomic_add_fetch((i32 *)_addend, _val, __ATOMIC_SEQ_CST)
|
|
#define AtomicAdd64(_addend, _val) __atomic_add_fetch((i64 *)_addend, _val, __ATOMIC_SEQ_CST)
|
|
#define AtomicStore(_ptr, _val) __atomic_store_n(_ptr, _val, __ATOMIC_SEQ_CST)
|
|
#define AtomicStoreRelease(_ptr, _val) __atomic_store_n(_ptr, _val, __ATOMIC_RELEASE)
|
|
#define AtomicLoad(_ptr) __atomic_load_n(_ptr, __ATOMIC_SEQ_CST)
|
|
#define AtomicLoadAcquire(_ptr) __atomic_load_n(_ptr, __ATOMIC_ACQUIRE)
|
|
#elif defined(_MSC_VER)
|
|
#define AtomicAdd32(_addend, _val) _InterlockedExchangeAdd((volatile long *)_addend, _val)
|
|
#define AtomicAdd64(_addend, _val) _InterlockedExchangeAdd64((volatile __int64 *)_addend, _val)
|
|
#define AtomicStore(_ptr, _val) _InterlockedExchange((volatile long *)_ptr, _val)
|
|
#define AtomicStoreRelease(_ptr, _val) _InterlockedExchange_HLERelease(_ptr, _val)
|
|
#define AtomicLoad(_ptr) _InterlockedOr(_ptr, 0)
|
|
#define AtomicLoadAcquire(_ptr) _InterlockedOr_HLEAcquire(_ptr, 0)
|
|
#endif
|
|
|
|
/* Threading wrapper */
|
|
typedef struct thread thread;
|
|
|
|
/* Win32 uses DWORD as the return type, Linux uses void *.
|
|
* I don't think that I've ever used a threads return value... */
|
|
#ifdef _WIN32
|
|
#define THREAD_RETURN_TYPE u32
|
|
#elif defined(__linux__)
|
|
#define THREAD_RETURN_TYPE void *
|
|
#endif
|
|
|
|
/* Generates a thread entry point */
|
|
#define THREAD_FN(_name) THREAD_RETURN_TYPE _name(void *param)
|
|
typedef THREAD_FN(thread_fn);
|
|
/* Starts a new thread */
|
|
RTC_API thread *StartThread(thread_fn *fn, void *param);
|
|
/* Waits until a thread terminates, returns its return value. */
|
|
RTC_API THREAD_RETURN_TYPE JoinThread(thread *t);
|
|
|
|
#endif
|
|
|
|
#ifdef RT_CORE_IMPLEMENTATION
|
|
#undef RT_CORE_IMPLEMENTATION
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
#ifdef __linux__
|
|
#include <pthread.h>
|
|
#elif defined(_WIN32)
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
/* Alloc */
|
|
RTC_API void *
|
|
ArenaAlloc(arena *a, isize size, isize align, isize n, int flags)
|
|
{
|
|
isize padding = -(usize)a->begin & (align - 1);
|
|
isize available = a->end - a->begin - padding;
|
|
if (available < 0 || n > available / size)
|
|
{
|
|
if (!(flags & ALLOC_SOFTFAIL))
|
|
abort();
|
|
return NULL;
|
|
}
|
|
void *p = a->begin + padding;
|
|
a->begin += padding - n * size;
|
|
return (flags & ALLOC_NOZERO) ? p : memset(p, 0, n * size);
|
|
}
|
|
|
|
/* S8 funcs */
|
|
RTC_API s8
|
|
S8Span(u8* begin, u8* end)
|
|
{
|
|
s8 s = {0};
|
|
s.data = begin;
|
|
s.length = begin ? end - begin : 0;
|
|
return s;
|
|
}
|
|
RTC_API b32
|
|
S8Equals(s8 a, s8 b)
|
|
{
|
|
return a.length == b.length && (!a.length || (memcmp(a.data, b.data, a.length) == 0));
|
|
}
|
|
RTC_API isize
|
|
S8Compare(s8 a, s8 b)
|
|
{
|
|
isize n = min(a.length, b.length);
|
|
for (isize i = 0; i < n; ++i)
|
|
{
|
|
if (a.data[i] != b.data[i])
|
|
return (isize)a.data[i] - (isize)b.data[i];
|
|
}
|
|
return a.length - b.length;
|
|
}
|
|
RTC_API u64
|
|
S8Hash(s8 s)
|
|
{
|
|
u64 hash = 0xcbf29ce484222325;
|
|
for (isize i = 0; i < s.length; ++i)
|
|
hash = (hash ^ s.data[i]) * 0x00000100000001b3;
|
|
return hash;
|
|
}
|
|
RTC_API s8
|
|
S8Trim(s8 s)
|
|
{
|
|
for (; s.length && *s.data <= ' '; s.data++, s.length--)
|
|
{
|
|
}
|
|
for (; s.length && s.data[s.length - 1] <= ' '; s.length--)
|
|
{
|
|
}
|
|
return s;
|
|
}
|
|
RTC_API s8
|
|
S8TrimLeft(s8 s)
|
|
{
|
|
for (; s.length && *s.data <= ' '; s.data++, s.length--)
|
|
{
|
|
}
|
|
return s;
|
|
}
|
|
RTC_API s8
|
|
S8TrimRight(s8 s)
|
|
{
|
|
for (; s.length && s.data[s.length - 1] <= ' '; s.length--)
|
|
{
|
|
}
|
|
return s;
|
|
}
|
|
RTC_API u8 *
|
|
S8Chr(s8 s, u8 c)
|
|
{
|
|
for (isize i = 0; i < s.length; ++i)
|
|
{
|
|
if (s.data[i] == c)
|
|
return &s.data[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
RTC_API u8 *
|
|
S8End(s8 s)
|
|
{
|
|
return &s.data[s.length - 1];
|
|
}
|
|
RTC_API split_result
|
|
S8Split(s8 s, u8 c)
|
|
{
|
|
u8 *chr = S8Chr(s, c);
|
|
if (!chr)
|
|
return (split_result){s};
|
|
return (split_result){
|
|
S8Span(s.data, chr),
|
|
S8Span(chr + 1, S8End(s) + 1),
|
|
};
|
|
}
|
|
RTC_API s8
|
|
S8Clone(s8 s, arena *a)
|
|
{
|
|
s8 c = {0};
|
|
c.data = alloc(a, u8, s.length);
|
|
c.length = s.length;
|
|
memcpy(c.data, s.data, s.length);
|
|
return s;
|
|
}
|
|
|
|
/* Basic file io */
|
|
RTC_API file_buffer
|
|
ReadEntireFile(s8 path, arena *a)
|
|
{
|
|
char _p[260];
|
|
if (path.length >= countof(_p))
|
|
return (file_buffer){0};
|
|
memcpy(_p, path.data, path.length);
|
|
_p[path.length] = '\0';
|
|
FILE *f = fopen(_p, "rb");
|
|
if (!f)
|
|
return (file_buffer){0};
|
|
size_t max = (size_t)(a->end - a->begin);
|
|
size_t num = fread(a->begin, 1, max, f);
|
|
if (ferror(f))
|
|
{
|
|
fclose(f);
|
|
return (file_buffer){0};
|
|
}
|
|
fclose(f);
|
|
file_buffer fbuf = {
|
|
.data = (byte *)a->begin,
|
|
.length = (isize)num,
|
|
};
|
|
a->begin += num;
|
|
return fbuf;
|
|
}
|
|
RTC_API s8
|
|
ReadEntireFileS8(s8 path, arena *a)
|
|
{
|
|
file_buffer fb = ReadEntireFile(path, a);
|
|
return (s8){
|
|
.data = (u8 *)fb.data,
|
|
.length = fb.length,
|
|
};
|
|
}
|
|
RTC_API b32
|
|
WriteEntireFile(s8 path, byte *data, isize length)
|
|
{
|
|
char _p[260];
|
|
if (path.length >= countof(_p))
|
|
return 0;
|
|
memcpy(_p, path.data, path.length);
|
|
_p[path.length] = '\0';
|
|
FILE *f = fopen(_p, "wb");
|
|
if (!f)
|
|
return 0;
|
|
size_t num = fwrite(data, (size_t)length, 1, f);
|
|
fclose(f);
|
|
return num == 1;
|
|
}
|
|
|
|
/* Threading */
|
|
#ifdef __linux__
|
|
RTC_API thread *
|
|
StartThread(thread_fn *fn, void *param)
|
|
{
|
|
pthread_t t;
|
|
if (pthread_create(&t, NULL, fn, param) != 0)
|
|
return NULL;
|
|
return (thread *)(usize)t;
|
|
}
|
|
RTC_API THREAD_RETURN_TYPE
|
|
JoinThread(thread *t)
|
|
{
|
|
void *ret = NULL;
|
|
pthread_join((pthread_t)(usize)t, &ret);
|
|
return ret;
|
|
}
|
|
|
|
#elif defined(_WIN32)
|
|
RTC_API thread *
|
|
StartThread(thread_fn *fn, void *param)
|
|
{
|
|
HANDLE h = CreateThread(
|
|
NULL,
|
|
0, /* Use default stack size */
|
|
(LPTHREAD_START_ROUTINE)fn,
|
|
param,
|
|
0,
|
|
NULL);
|
|
return (thread *)h;
|
|
}
|
|
RTC_API THREAD_RETURN_TYPE
|
|
JoinThread(thread *t)
|
|
{
|
|
HANDLE h = (HANDLE)t;
|
|
if (WaitForSingleObject(h, INFINITE) == WAIT_OBJECT_0)
|
|
{
|
|
DWORD exit;
|
|
GetExitCodeThread(h, &exit);
|
|
CloseHandle(h);
|
|
return exit;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|