856 lines
20 KiB
C
856 lines
20 KiB
C
// gb.h - v0.01 - public domain C helper library - no warranty implied; use at your own risk
|
|
// (Experimental) A C helper library geared towards game development
|
|
|
|
/*
|
|
LICENSE
|
|
This software is in the public domain. Where that dedication is not
|
|
recognized, you are granted a perpetual, irrevocable license to copy,
|
|
distribute, and modify this file as you see fit.
|
|
|
|
WARNING
|
|
- This library is _highly_ experimental and features may not work as expected.
|
|
- This also means that many functions are not documented.
|
|
|
|
CONTENTS
|
|
- Common Macros
|
|
- Assert
|
|
- Types
|
|
- Cast macro (easy grepping)
|
|
- Memory
|
|
- Custom Allocation
|
|
- gb_Allocator
|
|
- gb_Arena
|
|
- gb_Pool
|
|
- gb_String
|
|
|
|
TODO
|
|
- Mutex
|
|
- Atomics
|
|
- Semaphore
|
|
- Thread
|
|
- OS Types and Functions (File/IO/OS/etc.)
|
|
*/
|
|
|
|
/*
|
|
Version History:
|
|
0.01 - Initial Version
|
|
*/
|
|
|
|
|
|
#ifndef GB_INCLUDE_GB_H
|
|
#define GB_INCLUDE_GB_H
|
|
|
|
#if defined(__cplusplus)
|
|
extern "C" {
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
#include <stddef.h>
|
|
|
|
// NOTE(bill): Because static means three different things in C/C++
|
|
// Great design(!)
|
|
#ifndef local_persist
|
|
#define global static
|
|
#define internal static
|
|
#define local_persist static
|
|
#endif
|
|
|
|
// NOTE(bill): If I put gb_inline, I _want_ it inlined
|
|
#ifndef gb_inline
|
|
#if defined(_MSC_VER)
|
|
#define gb_inline __forceinline
|
|
#else
|
|
#define gb_inline __attribute__ ((__always_inline__))
|
|
#endif
|
|
#endif
|
|
|
|
|
|
#if !defined(GB_NO_STDIO)
|
|
#include <stdio.h>
|
|
#endif
|
|
|
|
#ifndef GB_ASSERT
|
|
#include <assert.h>
|
|
#define GB_ASSERT(cond) assert(cond)
|
|
#endif
|
|
|
|
#define GB_STATIC_ASSERT(cond, msg) typedef char gb__static_assertion_##msg[(!!(cond))*2-1]
|
|
// NOTE(bill): Token pasting madness
|
|
#define GB_COMPILE_TIME_ASSERT3(cond, line) GB_STATIC_ASSERT(cond, static_assertion_at_line_##line)
|
|
#define GB_COMPILE_TIME_ASSERT2(cond, line) GB_COMPILE_TIME_ASSERT3(cond, line)
|
|
#define GB_COMPILE_TIME_ASSERT(cond) GB_COMPILE_TIME_ASSERT2(cond, __LINE__)
|
|
|
|
|
|
#if !defined(GB_NO_STDIO) && defined(_MSC_VER)
|
|
// snprintf_msvc
|
|
gb_inline int
|
|
gb__vsnprintf_compatible(char* buffer, size_t size, char const *format, va_list args)
|
|
{
|
|
int result = -1;
|
|
if (size > 0)
|
|
result = _vsnprintf_s(buffer, size, _TRUNCATE, format, args);
|
|
if (result == -1)
|
|
return _vscprintf(format, args);
|
|
return result;
|
|
}
|
|
|
|
gb_inline int
|
|
gb__snprintf_compatible(char* buffer, size_t size, char const *format, ...)
|
|
{
|
|
int result = -1;
|
|
va_list args;
|
|
va_start(args, format);
|
|
result = gb__vsnprintf_compatible(buffer, size, format, args);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
#if !defined(GB_DO_NOT_USE_MSVC_SPRINTF_FIX)
|
|
#define snprintf gb__snprintf_compatible
|
|
#define vsnprintf gb__vsnprintf_compatible
|
|
#endif /* GB_DO_NOT_USE_MSVC_SPRINTF_FIX */
|
|
#endif /* !defined(GB_NO_STDIO) */
|
|
|
|
|
|
|
|
|
|
|
|
#if defined(_MSC_VER)
|
|
typedef unsigned __int8 u8;
|
|
typedef signed __int8 s8;
|
|
typedef unsigned __int16 u16;
|
|
typedef signed __int16 s16;
|
|
typedef unsigned __int32 u32;
|
|
typedef signed __int32 s32;
|
|
typedef unsigned __int64 u64;
|
|
typedef signed __int64 s64;
|
|
#else
|
|
#include <stdint.h>
|
|
typedef uint8_t u8;
|
|
typedef int8_t s8;
|
|
typedef uint16_t u16;
|
|
typedef int16_t s16;
|
|
typedef uint32_t u32;
|
|
typedef int32_t s32;
|
|
typedef uint64_t u64;
|
|
typedef int64_t s64;
|
|
#endif
|
|
|
|
GB_COMPILE_TIME_ASSERT(sizeof(s8) == 1);
|
|
GB_COMPILE_TIME_ASSERT(sizeof(s16) == 2);
|
|
GB_COMPILE_TIME_ASSERT(sizeof(s32) == 4);
|
|
GB_COMPILE_TIME_ASSERT(sizeof(s64) == 8);
|
|
|
|
typedef size_t usize;
|
|
|
|
typedef uintptr_t uintptr;
|
|
|
|
typedef float f32;
|
|
typedef double f64;
|
|
|
|
#include <stdbool.h> // NOTE(bill): To get false/true
|
|
|
|
// Boolean Types
|
|
typedef s8 b8;
|
|
typedef s32 b32;
|
|
|
|
|
|
|
|
#define U8_MIN 0u
|
|
#define U8_MAX 0xffu
|
|
#define S8_MIN (-0x7f - 1)
|
|
#define S8_MAX 0x7f
|
|
|
|
#define U16_MIN 0u
|
|
#define U16_MAX 0xffffu
|
|
#define S16_MIN (-0x7fff - 1)
|
|
#define S16_MAX 0x7fff
|
|
|
|
#define U32_MIN 0u
|
|
#define U32_MAX 0xffffffffu
|
|
#define S32_MIN (-0x7fffffff - 1)
|
|
#define S32_MAX 0x7fffffff
|
|
|
|
#define U64_MIN 0ull
|
|
#define U64_MAX 0xffffffffffffffffull
|
|
#define S64_MIN (-0x7fffffffffffffffll - 1)
|
|
#define S64_MAX 0x7fffffffffffffffll
|
|
|
|
|
|
|
|
|
|
|
|
#ifndef COUNT_OF
|
|
#define COUNT_OF(x) (sizeof((x)) / sizeof(0[(x)]))
|
|
#endif
|
|
|
|
// NOTE(bill): Allows for easy grep of casts
|
|
// NOTE(bill): Still not as type safe as C++ static_cast, reinterpret_cast, const_cast
|
|
#ifndef cast
|
|
#define cast(x) (x)
|
|
#endif
|
|
|
|
#ifndef NULL
|
|
#define NULL ((void *)0)
|
|
#endif
|
|
|
|
#ifndef GB_UNUSED
|
|
#define GB_UNUSED(x) ((void)(sizeof(x)))
|
|
#endif
|
|
|
|
#ifndef gb_inline
|
|
#if defined(_MSC_VER)
|
|
#define gb_inline __forceinline
|
|
#else
|
|
#define gb_inline __attribute__ ((__always_inline__))
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////
|
|
// //
|
|
// Memory //
|
|
// //
|
|
////////////////////////////////
|
|
|
|
#include <string.h> // For memcpy/memmove/memset/etc.
|
|
|
|
#ifndef GB_IS_POWER_OF_TWO
|
|
#define GB_IS_POWER_OF_TWO(x) ((x) != 0) && !((x) & ((x)-1))
|
|
#endif
|
|
|
|
void *gb_align_forward(void *ptr, usize align);
|
|
|
|
void gb_zero_size(void *ptr, usize size);
|
|
#define gb_zero_struct(t) gb_zero_size((t), sizeof(*(t))) // NOTE(bill): Pass pointer of struct
|
|
#define gb_zero_array(a, count) gb_zero_size((a), sizeof((a)[0])*count)
|
|
|
|
|
|
#if defined(GB_IMPLEMENTATION)
|
|
gb_inline void *
|
|
gb_align_forward(void *ptr, usize align)
|
|
{
|
|
uintptr p;
|
|
usize modulo;
|
|
|
|
GB_ASSERT(GB_IS_POWER_OF_TWO(align));
|
|
|
|
p = cast(uintptr)ptr;
|
|
modulo = p % align;
|
|
if (modulo) p += (align - modulo);
|
|
return cast(void *)p;
|
|
}
|
|
|
|
gb_inline void gb_zero_size(void *ptr, usize size) { memset(ptr, 0, size); }
|
|
|
|
#endif // GB_IMPLEMENTATION
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
////////////////////////////////
|
|
// //
|
|
// Custom Allocation //
|
|
// //
|
|
////////////////////////////////
|
|
|
|
typedef enum gb_Allocation_Type
|
|
{
|
|
GB_ALLOCATION_TYPE_ALLOC,
|
|
GB_ALLOCATION_TYPE_FREE,
|
|
GB_ALLOCATION_TYPE_FREE_ALL,
|
|
GB_ALLOCATION_TYPE_RESIZE,
|
|
} gb_Allocation_Type;
|
|
|
|
#define GB_ALLOCATOR_PROCEDURE(name) void *name(void *allocator_data, gb_Allocation_Type type, usize size, usize alignment, void *old_memory, usize old_size, u32 options)
|
|
typedef GB_ALLOCATOR_PROCEDURE(gb_Allocator_Procedure);
|
|
|
|
typedef struct gb_Allocator
|
|
{
|
|
gb_Allocator_Procedure *procedure;
|
|
void *data;
|
|
} gb_Allocator;
|
|
|
|
#ifndef GB_DEFAULT_ALIGNMENT
|
|
#define GB_DEFAULT_ALIGNMENT 8
|
|
#endif
|
|
|
|
gb_inline void *gb_alloc_align(gb_Allocator a, usize size, usize alignment) { return a.procedure(a.data, GB_ALLOCATION_TYPE_ALLOC, size, alignment, NULL, 0, 0); }
|
|
gb_inline void *gb_alloc(gb_Allocator a, usize size) { return gb_alloc_align(a, size, GB_DEFAULT_ALIGNMENT); }
|
|
gb_inline void gb_free(gb_Allocator a, void *ptr) { a.procedure(a.data, GB_ALLOCATION_TYPE_FREE, 0, 0, ptr, 0, 0); }
|
|
gb_inline void gb_free_all(gb_Allocator a) { a.procedure(a.data, GB_ALLOCATION_TYPE_FREE_ALL, 0, 0, NULL, 0, 0); }
|
|
gb_inline void *gb_resize(gb_Allocator a, void *ptr, usize new_size) { return a.procedure(a.data, GB_ALLOCATION_TYPE_RESIZE, new_size, 0, ptr, 0, 0); }
|
|
|
|
gb_inline void *gb_alloc_copy(gb_Allocator a, void* src, usize size) { return memcpy(gb_alloc(a, size), src, size); }
|
|
gb_inline void *gb_alloc_align_copy(gb_Allocator a, void* src, usize size, usize alignment) { return memcpy(gb_alloc_align(a, size, alignment), src, size); }
|
|
|
|
#define gb_alloc_struct(allocator, Type) (Type *)gb_alloc_align(allocator, sizeof(Type))
|
|
#define gb_alloc_array(allocator, Type, count) (Type *)gb_alloc(allocator, sizeof(Type) * (count))
|
|
|
|
|
|
|
|
|
|
typedef struct gb_Arena
|
|
{
|
|
gb_Allocator backing;
|
|
void *physical_start;
|
|
usize total_size;
|
|
usize total_allocated_count;
|
|
usize prev_allocated_count;
|
|
u32 temp_count;
|
|
} gb_Arena;
|
|
|
|
void gb_init_arena_from_memory(gb_Arena *arena, void *start, usize size);
|
|
void gb_init_arena_from_allocator(gb_Arena *arena, gb_Allocator backing, usize size);
|
|
void gb_free_arena(gb_Arena *arena);
|
|
|
|
gb_Allocator gb_make_arena_allocator(gb_Arena *arena);
|
|
GB_ALLOCATOR_PROCEDURE(gb_arena_allocator_procedure);
|
|
|
|
|
|
|
|
typedef struct gb_Temp_Arena_Memory
|
|
{
|
|
gb_Arena *arena;
|
|
usize original_count;
|
|
} gb_Temp_Arena_Memory;
|
|
|
|
gb_Temp_Arena_Memory gb_begin_temp_arena_memory(gb_Arena *arena);
|
|
void gb_end_temp_arena_memory(gb_Temp_Arena_Memory tmp_mem);
|
|
|
|
|
|
|
|
typedef struct gb_Pool
|
|
{
|
|
gb_Allocator backing;
|
|
|
|
void *physical_start;
|
|
void *free_list;
|
|
|
|
usize block_size;
|
|
usize block_align;
|
|
usize total_size;
|
|
} gb_Pool;
|
|
|
|
void gb_init_pool(gb_Pool *pool, gb_Allocator backing, usize num_blocks, usize block_size);
|
|
void gb_init_pool_align(gb_Pool *pool, gb_Allocator backing, usize num_blocks, usize block_size, usize block_align);
|
|
void gb_free_pool(gb_Pool *pool);
|
|
|
|
|
|
gb_Allocator gb_make_pool_allocator(gb_Pool *pool);
|
|
GB_ALLOCATOR_PROCEDURE(gb_pool_allocator_procedure);
|
|
|
|
|
|
|
|
|
|
#if defined(GB_IMPLEMENTATION)
|
|
|
|
gb_inline void
|
|
gb_init_arena_from_memory(gb_Arena *arena, void *start, usize size)
|
|
{
|
|
arena->backing.procedure = NULL;
|
|
arena->backing.data = NULL;
|
|
arena->physical_start = start;
|
|
arena->total_size = size;
|
|
arena->total_allocated_count = 0;
|
|
arena->temp_count = 0;
|
|
}
|
|
|
|
gb_inline void
|
|
gb_init_arena_from_allocator(gb_Arena *arena, gb_Allocator backing, usize size)
|
|
{
|
|
arena->backing = backing;
|
|
arena->physical_start = gb_alloc(backing, size);
|
|
arena->total_size = size;
|
|
arena->total_allocated_count = 0;
|
|
arena->temp_count = 0;
|
|
}
|
|
|
|
gb_inline void
|
|
gb_free_arena(gb_Arena *arena)
|
|
{
|
|
if (arena->backing.procedure) {
|
|
gb_free(arena->backing, arena->physical_start);
|
|
arena->physical_start = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
gb_inline gb_Allocator
|
|
gb_make_arena_allocator(gb_Arena *arena)
|
|
{
|
|
gb_Allocator allocator;
|
|
allocator.procedure = gb_arena_allocator_procedure;
|
|
allocator.data = arena;
|
|
return allocator;
|
|
}
|
|
|
|
|
|
GB_ALLOCATOR_PROCEDURE(gb_arena_allocator_procedure)
|
|
{
|
|
gb_Arena *arena = cast(gb_Arena *)allocator_data;
|
|
|
|
GB_UNUSED(options);
|
|
GB_UNUSED(old_size);
|
|
|
|
switch (type) {
|
|
case GB_ALLOCATION_TYPE_ALLOC: {
|
|
void *ptr;
|
|
usize actual_size = size + alignment;
|
|
|
|
// NOTE(bill): Out of memory
|
|
if (arena->total_allocated_count + actual_size > cast(usize)arena->total_size)
|
|
return NULL;
|
|
|
|
ptr = gb_align_forward(cast(u8 *)arena->physical_start + arena->total_allocated_count, alignment);
|
|
arena->prev_allocated_count = arena->total_allocated_count;
|
|
arena->total_allocated_count += actual_size;
|
|
return ptr;
|
|
} break;
|
|
|
|
case GB_ALLOCATION_TYPE_FREE: {
|
|
// NOTE(bill): Free all at once
|
|
// NOTE(bill): Use Temp_Arena_Memory if you want to free a block
|
|
} break;
|
|
|
|
case GB_ALLOCATION_TYPE_FREE_ALL:
|
|
arena->total_allocated_count = 0;
|
|
break;
|
|
|
|
case GB_ALLOCATION_TYPE_RESIZE: {
|
|
// TODO(bill): Check if ptr is at the top
|
|
void *ptr = gb_alloc_align(gb_make_arena_allocator(arena), size, alignment);
|
|
memcpy(ptr, old_memory, size);
|
|
return ptr;
|
|
} break;
|
|
}
|
|
|
|
return NULL; // NOTE(bill): Default return value
|
|
}
|
|
|
|
|
|
gb_inline gb_Temp_Arena_Memory
|
|
gb_begin_temp_arena_memory(gb_Arena *arena)
|
|
{
|
|
gb_Temp_Arena_Memory tmp;
|
|
tmp.arena = arena;
|
|
tmp.original_count = arena->total_allocated_count;
|
|
arena->temp_count++;
|
|
return tmp;
|
|
}
|
|
|
|
gb_inline void
|
|
gb_end_temp_arena_memory(gb_Temp_Arena_Memory tmp)
|
|
{
|
|
GB_ASSERT(tmp.arena->total_allocated_count >= tmp.original_count);
|
|
GB_ASSERT(tmp.arena->temp_count > 0);
|
|
tmp.arena->total_allocated_count = tmp.original_count;
|
|
tmp.arena->temp_count--;
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
gb_init_pool(gb_Pool *pool, gb_Allocator backing, usize num_blocks, usize block_size)
|
|
{
|
|
gb_init_pool_align(pool, backing, num_blocks, block_size, GB_DEFAULT_ALIGNMENT);
|
|
}
|
|
|
|
void
|
|
gb_init_pool_align(gb_Pool *pool, gb_Allocator backing, usize num_blocks, usize block_size, usize block_align)
|
|
{
|
|
memset(pool, 0, sizeof(gb_Pool));
|
|
|
|
pool->backing = backing;
|
|
pool->block_size = block_size;
|
|
pool->block_align = block_align;
|
|
|
|
usize actual_block_size = block_size + block_align;
|
|
usize pool_size = num_blocks * actual_block_size;
|
|
|
|
u8 *data = cast(u8 *)gb_alloc_align(backing, pool_size, block_align);
|
|
|
|
// Init intrusive freelist
|
|
u8 *curr = data;
|
|
for (usize block_index = 0; block_index < num_blocks-1; block_index++) {
|
|
uintptr *next = cast(uintptr *)curr;
|
|
*next = cast(uintptr)curr + actual_block_size;
|
|
curr += actual_block_size;
|
|
}
|
|
|
|
uintptr *end = cast(uintptr*)curr;
|
|
*end = cast(uintptr)NULL;
|
|
|
|
pool->physical_start = data;
|
|
pool->free_list = data;
|
|
}
|
|
|
|
void
|
|
gb_free_pool(gb_Pool *pool)
|
|
{
|
|
if (pool->backing.procedure) {
|
|
gb_free(pool->backing, pool->physical_start);
|
|
}
|
|
}
|
|
|
|
|
|
gb_Allocator
|
|
gb_make_pool_allocator(gb_Pool *pool)
|
|
{
|
|
gb_Allocator allocator;
|
|
allocator.procedure = gb_pool_allocator_procedure;
|
|
allocator.data = pool;
|
|
return allocator;
|
|
}
|
|
|
|
GB_ALLOCATOR_PROCEDURE(gb_pool_allocator_procedure)
|
|
{
|
|
gb_Pool *pool = cast(gb_Pool *)allocator_data;
|
|
|
|
GB_UNUSED(options);
|
|
GB_UNUSED(old_size);
|
|
|
|
switch (type) {
|
|
case GB_ALLOCATION_TYPE_ALLOC: {
|
|
uintptr next_free;
|
|
void *ptr;
|
|
GB_ASSERT(size == pool->block_size);
|
|
GB_ASSERT(alignment == pool->block_align);
|
|
GB_ASSERT(pool->free_list != NULL);
|
|
|
|
next_free = *cast(uintptr *)pool->free_list;
|
|
ptr = pool->free_list;
|
|
pool->free_list = cast(void *)next_free;
|
|
pool->total_size += pool->block_size;
|
|
return ptr;
|
|
} break;
|
|
|
|
case GB_ALLOCATION_TYPE_FREE: {
|
|
uintptr *next;
|
|
if (old_memory == NULL) return NULL;
|
|
|
|
next = cast(uintptr *)old_memory;
|
|
*next = cast(uintptr)pool->free_list;
|
|
pool->free_list = old_memory;
|
|
pool->total_size -= pool->block_size;
|
|
} break;
|
|
|
|
case GB_ALLOCATION_TYPE_FREE_ALL: {
|
|
// TODO(bill):
|
|
} break;
|
|
|
|
case GB_ALLOCATION_TYPE_RESIZE: {
|
|
// NOTE(bill): Cannot resize
|
|
} break;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#endif // GB_IMPLEMENTATION
|
|
|
|
|
|
////////////////////////////////
|
|
// //
|
|
// gb_String - C Compatible //
|
|
// //
|
|
////////////////////////////////
|
|
|
|
|
|
// Pascal like strings in C
|
|
typedef char *gb_String;
|
|
|
|
#ifndef GB_STRING_SIZE
|
|
#define GB_STRING_SIZE
|
|
typedef u32 gb_String_Size;
|
|
#endif
|
|
|
|
|
|
// This is stored at the beginning of the string
|
|
// NOTE(bill): It is (2*sizeof(gb_String_Size) + 2*sizeof(void*)) (default: 16B (32bit), 24B (64bit))
|
|
// NOTE(bill): If you only need a small string, just use a standard c string
|
|
typedef struct gb_String_Header
|
|
{
|
|
gb_Allocator allocator;
|
|
gb_String_Size length;
|
|
gb_String_Size capacity;
|
|
} gb_String_Header;
|
|
|
|
#define GB_STRING_HEADER(str) (cast(gb_String_Header *)str - 1)
|
|
|
|
gb_String gb_string_make(gb_Allocator a, char const *str);
|
|
gb_String gb_string_make_length(gb_Allocator a, void const *str, gb_String_Size num_bytes);
|
|
void gb_string_free(gb_String str);
|
|
|
|
gb_String gb_string_duplicate(gb_Allocator a, gb_String const str);
|
|
|
|
gb_String_Size gb_string_length(gb_String const str);
|
|
gb_String_Size gb_string_capacity(gb_String const str);
|
|
gb_String_Size gb_string_available_space(gb_String const str);
|
|
|
|
void gb_string_clear(gb_String str);
|
|
|
|
gb_String gb_string_append_string(gb_String str, gb_String const other);
|
|
gb_String gb_string_append_string_length(gb_String str, void const *other, gb_String_Size num_bytes);
|
|
gb_String gb_string_append_cstring(gb_String str, char const *other);
|
|
|
|
gb_String gb_string_set(gb_String str, char const *cstr);
|
|
|
|
gb_String gb_string_make_space_for(gb_String str, gb_String_Size add_len);
|
|
gb_String_Size gb_string_allocation_size(gb_String const str);
|
|
|
|
b32 gb_strings_are_equal(gb_String const lhs, gb_String const rhs);
|
|
|
|
gb_String gb_string_trim(gb_String str, char const *cut_set);
|
|
gb_String gb_string_trim_space(gb_String str); /* Whitespace ` \t\r\n\v\f` */
|
|
|
|
|
|
#if defined(GB_IMPLEMENTATION)
|
|
|
|
gb_inline void gb__string_set_length(gb_String str, gb_String_Size len) { GB_STRING_HEADER(str)->length = len; }
|
|
gb_inline void gb__string_set_capacity(gb_String str, gb_String_Size cap) { GB_STRING_HEADER(str)->capacity = cap; }
|
|
|
|
|
|
gb_inline gb_String
|
|
gb_string_make(gb_Allocator a, char const *str)
|
|
{
|
|
gb_String_Size len = cast(gb_String_Size)(str ? strlen(str) : 0);
|
|
return gb_string_make_length(a, str, len);
|
|
}
|
|
|
|
gb_String
|
|
gb_string_make_length(gb_Allocator a, void const *init_str, gb_String_Size num_bytes)
|
|
{
|
|
gb_String_Size header_size = sizeof(gb_String_Header);
|
|
void *ptr = gb_alloc(a, header_size + num_bytes + 1);
|
|
|
|
gb_String str;
|
|
gb_String_Header *header;
|
|
|
|
if (!init_str) gb_zero_size(ptr, header_size + num_bytes + 1);
|
|
if (ptr == NULL) return NULL;
|
|
|
|
str = cast(char *)ptr + header_size;
|
|
header = GB_STRING_HEADER(str);
|
|
header->allocator = a;
|
|
header->length = num_bytes;
|
|
header->capacity = num_bytes;
|
|
if (num_bytes && init_str)
|
|
memcpy(str, init_str, num_bytes);
|
|
str[num_bytes] = '\0';
|
|
|
|
return str;
|
|
}
|
|
|
|
gb_inline void
|
|
gb_string_free(gb_String str)
|
|
{
|
|
if (str) {
|
|
gb_String_Header *header;
|
|
header = GB_STRING_HEADER(str);
|
|
gb_free(header->allocator, header);
|
|
}
|
|
}
|
|
|
|
|
|
gb_inline gb_String gb_string_duplicate(gb_Allocator a, gb_String const str) { return gb_string_make_length(a, str, gb_string_length(str)); }
|
|
|
|
gb_inline gb_String_Size gb_string_length(gb_String const str) { return GB_STRING_HEADER(str)->length; }
|
|
gb_inline gb_String_Size gb_string_capacity(gb_String const str) { return GB_STRING_HEADER(str)->capacity; }
|
|
|
|
gb_inline gb_String_Size
|
|
gb_string_available_space(gb_String const str)
|
|
{
|
|
gb_String_Header *h = GB_STRING_HEADER(str);
|
|
if (h->capacity > h->length)
|
|
return h->capacity - h->length;
|
|
return 0;
|
|
}
|
|
|
|
|
|
gb_inline void gb_string_clear(gb_String str) { gb__string_set_length(str, 0); str[0] = '\0'; }
|
|
|
|
gb_inline gb_String gb_string_append_string(gb_String str, gb_String const other) { return gb_string_append_string_length(str, other, gb_string_length(other)); }
|
|
|
|
gb_String
|
|
gb_string_append_string_length(gb_String str, void const *other, gb_String_Size other_len)
|
|
{
|
|
gb_String_Size curr_len = gb_string_length(str);
|
|
|
|
str = gb_string_make_space_for(str, other_len);
|
|
if (str == NULL)
|
|
return NULL;
|
|
|
|
memcpy(str + curr_len, other, other_len);
|
|
str[curr_len + other_len] = '\0';
|
|
gb__string_set_length(str, curr_len + other_len);
|
|
|
|
return str;
|
|
}
|
|
|
|
gb_inline gb_String
|
|
gb_string_append_cstring(gb_String str, char const *other)
|
|
{
|
|
return gb_string_append_string_length(str, other, cast(gb_String_Size)strlen(other));
|
|
}
|
|
|
|
|
|
gb_String
|
|
gb_string_set(gb_String str, char const *cstr)
|
|
{
|
|
gb_String_Size len = cast(gb_String_Size)strlen(cstr);
|
|
if (gb_string_capacity(str) < len) {
|
|
str = gb_string_make_space_for(str, len - gb_string_length(str));
|
|
if (str == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
memcpy(str, cstr, len);
|
|
str[len] = '\0';
|
|
gb__string_set_length(str, len);
|
|
|
|
return str;
|
|
}
|
|
|
|
|
|
local_persist void *
|
|
gb__string_realloc(gb_Allocator a, void *ptr, gb_String_Size old_size, gb_String_Size new_size)
|
|
{
|
|
if (!ptr) return gb_alloc(a, new_size);
|
|
|
|
if (new_size < old_size)
|
|
new_size = old_size;
|
|
|
|
if (old_size == new_size) {
|
|
return ptr;
|
|
} else {
|
|
void *new_ptr = gb_alloc(a, new_size);
|
|
if (!new_ptr)
|
|
return NULL;
|
|
|
|
memcpy(new_ptr, ptr, old_size);
|
|
gb_free(a, ptr);
|
|
return new_ptr;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
gb_String
|
|
gb_string_make_space_for(gb_String str, gb_String_Size add_len)
|
|
{
|
|
gb_String_Size available = gb_string_available_space(str);
|
|
|
|
// Return if there is enough space left
|
|
if (available >= add_len) {
|
|
return str;
|
|
} else {
|
|
gb_String_Size new_len = gb_string_length(str) + add_len;
|
|
void *ptr = GB_STRING_HEADER(str);
|
|
gb_String_Size old_size = sizeof(struct gb_String_Header) + gb_string_length(str) + 1;
|
|
gb_String_Size new_size = sizeof(struct gb_String_Header) + new_len + 1;
|
|
|
|
void *new_ptr = gb__string_realloc(GB_STRING_HEADER(str)->allocator, ptr, old_size, new_size);
|
|
if (new_ptr == NULL) return NULL;
|
|
|
|
str = cast(char *)(GB_STRING_HEADER(new_ptr) + 1);
|
|
gb__string_set_capacity(str, new_len);
|
|
|
|
return str;
|
|
}
|
|
}
|
|
|
|
gb_inline gb_String_Size
|
|
gb_string_allocation_size(gb_String const str)
|
|
{
|
|
gb_String_Size cap = gb_string_capacity(str);
|
|
return sizeof(gb_String_Header) + cap;
|
|
}
|
|
|
|
|
|
gb_inline b32
|
|
gb_strings_are_equal(gb_String const lhs, gb_String const rhs)
|
|
{
|
|
gb_String_Size lhs_len, rhs_len, i;
|
|
lhs_len = gb_string_length(lhs);
|
|
rhs_len = gb_string_length(rhs);
|
|
if (lhs_len != rhs_len)
|
|
return false;
|
|
|
|
for (i = 0; i < lhs_len; i++) {
|
|
if (lhs[i] != rhs[i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
gb_String
|
|
gb_string_trim(gb_String str, char const *cut_set)
|
|
{
|
|
char *start, *end, *start_pos, *end_pos;
|
|
gb_String_Size len;
|
|
|
|
start_pos = start = str;
|
|
end_pos = end = str + gb_string_length(str) - 1;
|
|
|
|
while (start_pos <= end && strchr(cut_set, *start_pos))
|
|
start_pos++;
|
|
while (end_pos > start_pos && strchr(cut_set, *end_pos))
|
|
end_pos--;
|
|
|
|
len = cast(gb_String_Size)((start_pos > end_pos) ? 0 : ((end_pos - start_pos)+1));
|
|
|
|
if (str != start_pos)
|
|
memmove(str, start_pos, len);
|
|
str[len] = '\0';
|
|
|
|
gb__string_set_length(str, len);
|
|
|
|
return str;
|
|
}
|
|
|
|
gb_inline gb_String gb_string_trim_space(gb_String str) { return gb_string_trim(str, " \t\r\n\v\f"); }
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
////////////////////////////////
|
|
// //
|
|
// Unfinished code //
|
|
// //
|
|
////////////////////////////////
|
|
|
|
|
|
|
|
#if defined(__cplusplus)
|
|
}
|
|
#endif
|
|
|
|
#endif // GB_INCLUDE_GB_H
|