Browse Source

first commit

master
Nick Hayashi 1 year ago
commit
34e4fb08c3
  1. 206
      alloc.cpp
  2. 77
      alloc.h
  3. 275
      array.hpp
  4. 149
      cpuid.cpp
  5. 8
      cpuid.h
  6. 209
      file.cpp
  7. 49
      file.h
  8. 280
      print.cpp
  9. 155
      print.h
  10. 316
      serialize.cpp
  11. 208
      serialize.h
  12. 50
      signal-handler.h
  13. 979
      string.h
  14. 720
      table.hpp
  15. 111
      types.h
  16. 10
      util.h

206
alloc.cpp

@ -0,0 +1,206 @@
#include "alloc.h"
#include "string.h"
#include "print.h"
#include "types.h"
#if false
static void* leakcheckMalloc(size_t size, const char* file, s32 line) {
TYPES_H_FTAG;
return malloc(size);
}
static void* leakcheckCalloc(size_t maxNumOfElements, size_t elementSize, const char* file, s32 line) {
TYPES_H_FTAG;
return calloc(maxNumOfElements, elementSize);
}
static void* leakcheckRealloc(void* buffer, size_t newSize, const char* file, s32 line) {
TYPES_H_FTAG;
return realloc(buffer, newSize);
}
static void leakcheckFree(void* ptr, const char* file, s32 line) {
TYPES_H_FTAG;
free(ptr);
}
#define malloc(size) leakcheckMalloc(size, __FILE__, __LINE__)
#define calloc(numElements, size) leakcheckCalloc(numElements, size, __FILE__, __LINE__)
#define realloc(buffer, newSize) leakcheckRealloc(buffer, newSize, __FILE__, __LINE__)
#define free(ptr) leakcheckFree(ptr, __FILE__, __LINE__)
static void dumpLeaks() {
void* address;
size_t size;
const char* file;
s32 line;
for (;;) {
println("%p - %lu, %s:%d", address, size, file, line);
}
}
#endif
// system allocators
void* pMalloc(size_t size) {
TYPES_H_FTAG;
void* p = malloc(size);
if (!p) {
die("Out of memory!n\nfailed to malloc %p with size %u\n", p, size);
}
return p;
}
void* pMalloc(size_t size, void* allocatorState) {
TYPES_H_FTAG;
return pMalloc(size);
}
void* pCalloc(size_t maxNumOfElements, size_t elementSize) {
TYPES_H_FTAG;
void* p = calloc(maxNumOfElements, elementSize);
if (!p) {
die("Out of memory!\nfailed to calloc %p with %u elements of size %u\n", p, maxNumOfElements, elementSize);
}
return p;
}
void* pCalloc(size_t maxNumOfElements, size_t elementSize, void* allocatorState) {
TYPES_H_FTAG;
return pCalloc(maxNumOfElements, elementSize);
}
void* pRealloc(void* buffer, size_t newSize) {
TYPES_H_FTAG;
void* p = realloc(buffer, newSize);
if (!p) {
//pFree(buffer); // if we ever *don't* terminate the program at this point, we should free |buffer|
die("Out of memory!\nfailed to realloc %p with size: %u\n", buffer, newSize);
}
return p;
}
void* pRealloc(void* buffer, size_t newSize, void* allocatorState) {
TYPES_H_FTAG;
return pRealloc(buffer, newSize);
}
void pFree(void* ptr) {
TYPES_H_FTAG;
free(ptr);
}
void pFree(void* ptr, void* allocatorState) {
TYPES_H_FTAG;
pFree(ptr);
}
void pFree(const void* ptr) {
TYPES_H_FTAG;
pFree((void*) ptr);
}
void pFree(const void* ptr, void* allocatorState) {
TYPES_H_FTAG;
pFree((void*) ptr, allocatorState);
}
#ifdef malloc
#undef malloc
#endif
#ifdef calloc
#undef calloc
#endif
#ifdef realloc
#undef realloc
#endif
#ifdef free
#undef free
#endif
static bool DefaultAllocatorInited = false;
static Allocator DefaultAllocator;
static void defaultAllocatorInit() {
TYPES_H_FTAG;
DefaultAllocator.state = null;
DefaultAllocator.mallocate = pMalloc;
DefaultAllocator.callocate = pCalloc;
DefaultAllocator.reallocate = pRealloc;
DefaultAllocator.free = pFree;
DefaultAllocatorInited = true;
}
Allocator* Allocator::GetDefault() {
TYPES_H_FTAG;
if (!DefaultAllocatorInited) defaultAllocatorInit();
return &DefaultAllocator;
}
//================================================================================
// alignment should be a power of 2
static u64 alignForward2(u64 ptr, size_t alignment) {
TYPES_H_FTAG;
u64 p, a, modulo;
p = ptr;
a = alignment;
modulo = p & (a - 1);
if (modulo != 0) {
p += a - modulo;
}
return p;
}
static u64 alignForward(u64 ptr, size_t alignment) {
TYPES_H_FTAG;
return ((ptr + alignment - 1) / alignment) * alignment;
}
//================================================================================
// Scratch/Arena
Arena* Arena::Init(u32 sizeInBytes) {
TYPES_H_FTAG;
Arena* arena = (Arena*) pMalloc(sizeof(Arena));
arena->index = 0;
arena->buffer = (u8*) pMalloc(sizeof(u8) * sizeInBytes);
arena->bufferSizeInBytes = sizeInBytes;
return arena;
}
void* Arena::Alloc(u32 sizeInBytes) {
TYPES_H_FTAG;
u8* p = this->buffer + this->index;
u32 offset = (u32) alignForward2((u64) p, 64);
if ((void*)(offset + sizeInBytes) <= (this->buffer + this->bufferSizeInBytes)) {
void* ptr = &this->buffer[offset];
this->index += offset + sizeInBytes;
String::memset(ptr, 0, sizeInBytes);
return ptr;
}
return null;
}
void Arena::Clear() {
TYPES_H_FTAG;
this->index = 0;
}
//================================================================================
struct StackAllocator {
u32 bufferSize;
u32 index;
u8* buffer;
};
struct PoolAllocator {
u8* buffer;
};

77
alloc.h

@ -0,0 +1,77 @@
#ifndef ALLOC_H
#define ALLOC_H
#include "types.h"
// define a consistent memory allocation interface
// trailing void* is a pointer to some allocator state, if relevant.
// will be unused for malloc/calloc/realloc/free, as the allocator state is internal to the OS.
// overloads should exist which do nothing with the trailing paramter.
typedef void* (*mallocator) (size_t, void*);
typedef void* (*callocator) (size_t, size_t, void*);
typedef void* (*reallocator) (void*, size_t, void*);
typedef void (*freeer) (void*, void*);
typedef void (*clearer) ( void*);
typedef void (*destroyer) ( void*);
// operating system allocator wrappers
extern void* pMalloc(size_t size);
extern void* pMalloc(size_t size, void* allocatorState);
extern void* pCalloc(size_t maxNumOfElements, size_t elementSize);
extern void* pCalloc(size_t maxNumOfElements, size_t elementSize, void* allocatorState);
extern void* pRealloc(void* buffer, size_t newSize);
extern void* pRealloc(void* buffer, size_t newSize, void* allocatorState);
extern void pFree(void* ptr);
extern void pFree(void* ptr, void* allocatorState);
extern void pFree(const void* ptr);
extern void pFree(const void* ptr, void* allocatorState);
struct Allocator {
void* state;
mallocator mallocate;
callocator callocate;
reallocator reallocate;
freeer free; // releases a specific piece of memory
clearer clear; // should release all the memory owned by this allocator at once.
destroyer destroy; // releases all the memory owned by this allocator, and also destroys the allocator.
static Allocator* GetDefault();
Allocator() {
this->state = null;
this->mallocate = pMalloc;
this->callocate = pCalloc;
this->reallocate = pRealloc;
this->free = pFree;
this->clear = null;
this->destroy = null;
}
Allocator(mallocator mallocate, freeer free) {
this->state = null;
this->mallocate = mallocate;
this->callocate = null;
this->reallocate = null;
this->free = free;
this->clear = null;
this->destroy = null;
}
};
struct Arena {
u32 bufferSizeInBytes;
u32 index;
u8* buffer;
static Arena* Init(u32 sizeInBytes = 1024);
void* Alloc(u32 sizeInBytes);
void Clear();
};
#endif

275
array.hpp

@ -0,0 +1,275 @@
#ifndef ARRAY_H
#define ARRAY_H
#include <new> // operator new, operator delete
#include "alloc.h" // allocators...
#include "serialize.h" // serialization
#include "string.h" // String::memcpy
#include "types.h" // type definitions
// this is a dynamic array (grows as needed)
// should work with any data type for T including primitive types
// some initial |capacity| is heap-allocated and a pointer is stored to it as |data|
// the |length| of the array, or number of filled slots is also tracked.
//
// it implements a single constructor, and operator new. no destructor, or operator overloading besides that.
// remember to use ->data or .data to actually access the underlying array.
//
// overhead:
// size of the struct will be 128 bits on 64-bit platforms
// note that if you heap allocate this structure and store it on another struct, you will have to chase two pointers to get at the data.
// to avoid this, I often include this struct in other structs so there's only one pointer dereference,
// just like including a raw pointer array + length in your struct.
// because I like to do this, automatic destructors are not useful.
//
template <typename T>
struct Array {
u32 length;
u32 capacity;
T* data;
Array<T>(u32 _capacity = 8) {
TYPES_H_FTAG;
this->length = 0;
this->capacity = _capacity;
this->data = (T*) pCalloc(sizeof (T), _capacity);
}
void* operator new(size_t size) {
TYPES_H_FTAG;
return pMalloc((u32) size);
}
void checkIfShouldGrow() {
TYPES_H_FTAG;
if (this->isFull()) {
// optimal number as you approach infinite elements approaches PHI, but 1.5 sometimes works better for finite sizes
// more testing is probably needed
this->capacity = (u32) (this->capacity * 1.5);
this->data = (T*) pRealloc(data, sizeof(T) * this->capacity);
}
}
// for when the order in the array doesn't matter, move the end of the array into the removed slot
void removeSwapWithEnd(u32 index) {
TYPES_H_FTAG;
if (this->isEmpty()) return; // overhead, maybe assert instead?
u32 end = this->length - 1;
if (index != end) {
this->data[index] = this->data[end];
}
this->pop();
}
void removeSwapWithEnd(T* addr) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->length; i++) {
if ((this->data + i) == addr) {
removeSwapWithEnd(i);
return;
}
}
}
void removeAndShrink(u32 index) {
TYPES_H_FTAG;
for (u32 i = index + 1; i < this->length; i++) {
String::memcpy(this->data[i - 1], this->data[i], sizeof(T));
}
this->length--;
}
void removeAndShrink(T* elementAddr) {
TYPES_H_FTAG;
s32 index = -1;
for (u32 i = 0; i < this->length; i++) {
if ((this->data + i) == elementAddr) {
index = i;
break;
}
}
if (index == -1) {
return;
}
for (u32 i = index + 1; i < this->length; i++) {
String::memcpy((void*)(this->data + i - 1), (void*)(this->data + i), sizeof(T));
}
this->length--;
}
T pop() {
TYPES_H_FTAG;
if (this->isEmpty()) {
die("empty");
}
return this->data[--this->length];
}
// sometimes, you want to copy some POD data on the stack to the next position in the internal array
// that's what this does
u32 pushCopy(T* e) {
TYPES_H_FTAG;
this->checkIfShouldGrow();
String::memcpy((void*) &this->data[this->length++], e, sizeof(T));
return this->length - 1;
}
// returns the next address into which you can store a T. makes sure there's enough room first.
// it is irresponsible to call this and then not store a T in that address. this increments length,
// reserving the next spot for you.
T* pushNextAddrPromise() {
TYPES_H_FTAG;
this->checkIfShouldGrow();
return &this->data[this->length++];
}
u32 push(T e) {
TYPES_H_FTAG;
this->checkIfShouldGrow();
this->data[this->length++] = e;
return this->length - 1;
}
u32 pushMany(T* elements, u32 count) {
TYPES_H_FTAG;
// ensure we have capacity. if we have to realloc multiple times that can suck,
// but should be avoidable in practice by having an appropriately large initial capacity
while (this->capacity < (this->length + count)) {
this->capacity *= 1.5;
this->data = (T*) pRealloc(data, sizeof (T) * this->capacity);
}
u32 start = this->length;
for (u32 i1 = start, i2 = 0; i1 < count; i1++, i2++) {
this->data[this->length++] = elements[i2];
}
return start;
}
void reverse() {
TYPES_H_FTAG;
u32 count = this->length / 2;
for (u32 i = 0; i < count; i++) {
u32 offset = this->length - 1 - i;
T temp = this->data[i];
this->data[i] = this->data[offset];
this->data[offset] = temp;
}
}
T shift() {
TYPES_H_FTAG;
if (this->length == 0) {
return null;
}
T out = this->data[0];
this->length -= 1;
for (u32 i = 0; i < this->length; i++) {
*(this->data + i) = *(this->data + i + 1);
}
return out;
}
T unshift(T e) {
TYPES_H_FTAG;
this->checkIfShouldGrow();
for (u32 i = 0; i < this->length; i++) {
*(this->data + i + 1) = *(this->data + i);
}
this->data[0] = e;
this->length += 1;
return this->length;
}
T peek() const {
TYPES_H_FTAG;
if (this->isEmpty()) {
return null;
}
return this->data[this->length - 1];
}
bool isEmpty() const {
TYPES_H_FTAG;
return this->length == 0;
}
bool isFull() const {
TYPES_H_FTAG;
return this->length == this->capacity;
}
void clear() {
TYPES_H_FTAG;
this->length = 0;
}
};
template <typename T>
static void serialize(String* str, Array<T> array) {
TYPES_H_FTAG;
serialize(str, array.length);
serialize(str, array.capacity);
for (u32 i = 0; i < array.length; i++) {
serialize(str, array.data[i]);
}
}
template <typename T>
static void serialize(String* str, Array<T>* array) {
TYPES_H_FTAG;
SERIALIZE_HANDLE_NULL(str, array);
serialize(str, array->length);
serialize(str, array->capacity);
for (u32 i = 0; i < array->length; i++) {
serialize(str, array->data[i]);
}
}
template <typename T>
static void deserialize(char** buffer, Array<T>* array) {
TYPES_H_FTAG;
deserialize(buffer, &array->length);
deserialize(buffer, &array->capacity);
for (u32 i = 0; i < array->length; i++) {
deserialize(buffer, array->data + i);
}
}
template <typename T>
static void deserialize(char** buffer, Array<T>** array) {
TYPES_H_FTAG;
DESERIALIZE_HANDLE_NULL(buffer, array);
u32 length, capacity;
deserialize(buffer, &length);
deserialize(buffer, &capacity);
Array<T>* _array = new Array<T>(capacity);
_array->length = length;
for (u32 i = 0; i < _array->length; i++) {
deserialize(buffer, _array->data + i);
}
*array = _array;
}
#endif

149
cpuid.cpp

@ -0,0 +1,149 @@
#include "types.h"
#include "string.h"
#include "print.h"
static const char* szFeatures[] = {
"x87 FPU On Chip",
"Virtual-8086 Mode Enhancement",
"Debugging Extensions",
"Page Size Extensions",
"Time Stamp Counter",
"RDMR and WRMSR Support",
"Physical Address Extensions",
"Machine Check Exception",
"CMPXCHG8B Instruction",
"APIC On Chip",
"Unknown1",
"SYSENTER and SYSEXIT",
"Memory Type Range Registers",
"PTE Global Bit",
"Machine Check Architecture",
"Conditional Move/Compare Instruction",
"Page Attribute Table",
"36-bit Page Size Extension",
"Processor Serial Number",
"CFLUSH Extension",
"Unknown2",
"Debug Store",
"ThermalMonitor and Clock Ctrl",
"MMX Technology",
"FXSAVE/FXRSTOR",
"SSE Extensions",
"SSE2 Extensions",
"Self Snoop",
"Multithreading Technology",
"Thermal Monitor",
"Unknown4",
"Pending Break Enable"
};
#if defined(_WIN32)
// implementation originally from:
// https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/hskdteyh(v=vs.90)?redirectedfrom=MSDN
#include <intrin.h>
void cpuid() {
TYPES_H_FTAG;
int nSteppingID = 0;
int nModel = 0;
int nFamily = 0;
int nProcessorType = 0;
int nExtendedmodel = 0;
int nExtendedfamily = 0;
int nBrandIndex = 0;
int nCLFLUSHcachelinesize = 0;
int nLogicalProcessors = 0;
int nAPICPhysicalID = 0;
int nFeatureInfo = 0;
int nCacheLineSize = 0;
int nL2Associativity = 0;
int nCacheSizeK = 0;
int nPhysicalAddress = 0;
int nVirtualAddress = 0;
int nRet = 0;
int nCores = 0;
int nCacheType = 0;
int nCacheLevel = 0;
int nMaxThread = 0;
int nSysLineSize = 0;
int nPhysicalLinePartitions = 0;
int nWaysAssociativity = 0;
int nNumberSets = 0;
unsigned nIds, nExIds, i;
bool bSSE3Instructions = false;
bool bMONITOR_MWAIT = false;
bool bCPLQualifiedDebugStore = false;
bool bVirtualMachineExtensions = false;
bool bEnhancedIntelSpeedStepTechnology = false;
bool bThermalMonitor2 = false;
bool bSupplementalSSE3 = false;
bool bL1ContextID = false;
bool bCMPXCHG16B = false;
bool bxTPRUpdateControl = false;
bool bPerfDebugCapabilityMSR = false;
bool bSSE41Extensions = false;
bool bSSE42Extensions = false;
bool bPOPCNT = false;
bool bMultithreading = false;
bool bLAHF_SAHFAvailable = false;
bool bCmpLegacy = false;
bool bSVM = false;
bool bExtApicSpace = false;
bool bAltMovCr8 = false;
bool bLZCNT = false;
bool bSSE4A = false;
bool bMisalignedSSE = false;
bool bPREFETCH = false;
bool bSKINITandDEV = false;
bool bSYSCALL_SYSRETAvailable = false;
bool bExecuteDisableBitAvailable = false;
bool bMMXExtensions = false;
bool bFFXSR = false;
bool b1GBSupport = false;
bool bRDTSCP = false;
bool b64Available = false;
bool b3DNowExt = false;
bool b3DNow = false;
bool bNestedPaging = false;
bool bLBRVisualization = false;
bool bFP128 = false;
bool bMOVOptimization = false;
bool bSelfInit = false;
bool bFullyAssociative = false;
int cpuinfo[4] = { -1 };
__cpuid(cpuinfo, 0);
unsigned count = cpuinfo[0];
char cpustring[0x20];
String::memset((void*) cpustring, '\0', sizeof(cpustring));
*((int*) cpustring) = cpuinfo[1];
*((int*) (cpustring + 4)) = cpuinfo[3];
*((int*) (cpustring + 8)) = cpuinfo[2];
println("CPU Identification String: %s", cpustring);
//for (s32 i = 0; i < count; i++) {
// __cpuid(cpuinfo, i);
// println("info type: %d", i);
// println("cpuinfo[0] = 0x%x", cpuinfo[0]);
// println("cpuinfo[1] = 0x%x", cpuinfo[1]);
// println("cpuinfo[2] = 0x%x", cpuinfo[2]);
// println("cpuinfo[3] = 0x%x", cpuinfo[3]);
//}
}
#else
void cpuid() {
TYPES_H_FTAG;
}
#endif

8
cpuid.h

@ -0,0 +1,8 @@
#ifndef CPUID_H
#define CPUID_H
void cpuid();
#endif

209
file.cpp

@ -0,0 +1,209 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h> // fopen, fseek, ftell, fclose
#undef _CRT_SECURE_NO_WARNINGS
#include "alloc.h"
#include "array.hpp"
#include "file.h"
#include "print.h"
#include "types.h"
FILE* File::Open(const char* path, const char* mode) {
TYPES_H_FTAG;
return fopen(path, mode);
}
FILE* File::Open(const char* path, size_t* outSize, const char* mode) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, mode);
if (fp == null) {
return null;
}
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
*outSize = ftell(fp);
fseek(fp, 0L, SEEK_SET);
return fp;
}
void File::Close(FILE* file) {
fclose(file);
}
size_t File::Size(const char* path) {
TYPES_H_FTAG;
FILE* fp = File::Open(path);
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
fclose(fp);
return size;
}
size_t File::Size(FILE* fp) {
TYPES_H_FTAG;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
return size;
}
u8* File::Read(const char* path) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, "rb");
if (fp == null) {
return null;
}
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
u32 size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
char* buffer = (char*) pMalloc(size + 1);
fread(buffer, sizeof (char), size + 1, fp);
buffer[size] = '\0';
fclose(fp);
return (u8*) buffer;
}
u8* File::Read(const char* path, size_t* outSize) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, "rb");
if (fp == null) {
return null;
}
// get the file's size in bytes
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
// if we got a valid pointer to a size_t we should report the size back.
if (outSize != null) {
*outSize = size;
}
fseek(fp, 0L, SEEK_SET);
char* buffer = (char*) pMalloc(size + 1);
fread(buffer, sizeof (char), size + 1, fp);
buffer[size] = '\0';
fclose(fp);
return (u8*) buffer;
}
size_t File::Read(FILE* fp, void* destination) {
TYPES_H_FTAG;
fseek(fp, 0, SEEK_END);
size_t size = ftell(fp);
fseek(fp, 0L, SEEK_SET);
fread(destination, sizeof (char), size + 1, fp);
return size;
}
size_t File::Read(FILE* fp, void* destination, size_t size) {
TYPES_H_FTAG;
return fread(destination, sizeof (char), size + 1, fp);
}
s32 File::Write(const char* path, char* data, u32 count) {
TYPES_H_FTAG;
FILE* fp = File::Open(path, "wb");
if (fp == null) {
return -1; // failed to open the file
}
size_t writtenCount = fwrite(data, 1, count, fp);
fclose(fp);
if (writtenCount != count) {
return -2; // wrote only partially
}
return 0;
}
#if _WIN32
#include <windows.h>
// writes the filenames into the provided array |outFileNames|, must be allocated ahead of time.
void File::GetFileNamesInFolder(const char* path, Array<char*>* outFileNames) {
TYPES_H_FTAG;
massert(path != null, "provided 'null' for path argument");
massert(outFileNames != null, "provided 'null' for array argument");
WIN32_FIND_DATAA findData;
HANDLE hFind = INVALID_HANDLE_VALUE;
String str;
if (path[String::len(path) - 1] != '/') {
str = String64f("%s/*", path);
} else {
str = String64f("%s*", path);
}
hFind = FindFirstFileA(str.c_str(), &findData);
if (hFind == INVALID_HANDLE_VALUE) die("failed to open folder: %s", path);
while (FindNextFileA(hFind, &findData) != 0) {
if (findData.cFileName[0] != '.') {
outFileNames->push(String::cpy(findData.cFileName));
}
}
FindClose(hFind);
}
#else
#include <dirent.h>
void File::GetFileNamesInFolder(const char* path, Array<char*>* outFileNames) {
TYPES_H_FTAG;
massert(path != null, "provided 'null' for path argument");
massert(outFileNames != null, "provided 'null' for array argument");
DIR* dir = opendir(path);
struct dirent* d;
if (dir == null) die("failed to open folder: %s", path);
do {
d = readdir(dir);
if (d == null) break;
if (d->d_name[0] != '.') outFileNames->push(String::cpy(d->d_name));
} while (1);
closedir(dir);
}
#endif
#include <sys/types.h>
#include <sys/stat.h>
#ifndef WIN32
#include <unistd.h>
#endif
// msvc provides a 'stat' equivalent as _stat.
#ifdef WIN32
#define stat _stat
#endif
time_t File::LastModified(const char* path) {
TYPES_H_FTAG;
struct stat result;
if (stat(path, &result) == 0) {
return result.st_mtime;
}
massert(false, "failed to get last modified timestamp.");
return -1;
}
#undef stat
s32 File::Rename(const char* oldFilename, const char* newFilename) {
return rename(oldFilename, newFilename);
}
s32 File::Remove(const char* path) {
return remove(path);
}

49
file.h

@ -0,0 +1,49 @@
#ifndef FILE_H
#define FILE_H
#include <stdio.h> // FILE
#include <sys/types.h> // time_t
#include "array.hpp"
namespace File {
FILE* Open(const char* path, const char* mode = "rb");
FILE* Open(const char* path, size_t* outSize, const char* mode = "rb");
void Close(FILE* file);
size_t Size(const char* path);
size_t Size(FILE* fp);
// most commonly this is what you want - provide a filesystem path, read the whole file,
// and return a buffer with the file's contents.
// the file is opened and closed internally.
// the buffer you get returned is allocated internally and must be freed by the caller.
u8* Read(const char* path);
// if you also want to know the size of the file.
u8* Read(const char* path, size_t* outSize);
// for apis that want a more incremental approach, this function will read |file|'s contents into |destination|,
// which must be pre-allocated. the provided file must be closed by the caller.
// returns the number of bytes read.
size_t Read(FILE* fp, void* destination); // calls fseek to determine file size
size_t Read(FILE* fp, void* destination, size_t size); // you know and provide the file size
s32 Write(const char* path, char* data, u32 count);
// writes the filenames into the provided array |outFileNames|, must be allocated ahead of time.
void GetFileNamesInFolder(const char* path, Array<char*>* outFileNames);
// get the last-modified timestamp on a file located at 'path'.
// there's no caching done, so it's the real, current value.
// specifically, it's the field 'st_mtime' on the 'stat' struct.
time_t LastModified(const char* path);
s32 Rename(const char* oldFilename, const char* newFilename);
s32 Remove(const char* path);
}
#endif

280
print.cpp

@ -0,0 +1,280 @@
#include <stdarg.h> // va_list, va_start, va_end
#include <stdio.h> // FILE, stderr, stdout | vfprintf
#include <stdlib.h> // exit
#include "alloc.h"
#include "string.h"
#include "print.h"
#include "types.h"
void vprint(const char* format, va_list args) {
TYPES_H_FTAG;
vfprintf(stdout, format, args);
}
void vprintln(const char* format, va_list args) {
TYPES_H_FTAG;
vprint(format, args);
print("\n");
}
/**
* The entire purpose of this is so we don't have to #import <stdio.h> everywhere
* +we intend to replace printf at some point with this
*/
void print(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) { print("null"); return; }
va_list args;
va_start(args, format);
vprint(format, args);
va_end(args);
}
void println(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) { print("null\n"); return; }
va_list args;
va_start(args, format);
vprintln(format, args);
va_end(args);
}
/**
* Prints a stack trace.
* Implementation varies for Win32 vs. *nix
*/
#define BACKTRACE_MAX_FRAMES 63
#ifdef _WIN32
#include <windows.h>
#include <dbghelp.h>
// if |string| is non-null, then the stack trace will be concatenated to it instead of being printed to stdout.
void trace(String* string) {
TYPES_H_FTAG;
#define BACKTRACE_MAX_FUNCTION_NAME_LENGTH 1024
HANDLE processHandle = GetCurrentProcess();
SymInitialize(processHandle, null, true);
void* stack[BACKTRACE_MAX_FRAMES];
unsigned short numFrames = CaptureStackBackTrace(0, BACKTRACE_MAX_FRAMES, stack, null);
char buffer[sizeof(SYMBOL_INFO) + (BACKTRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*) buffer;
symbol->MaxNameLen = BACKTRACE_MAX_FUNCTION_NAME_LENGTH;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
DWORD displacement;
IMAGEHLP_LINE64 line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
for (u32 i = 0; i < numFrames; i++) {
DWORD64 address = (DWORD64) stack[i];
SymFromAddr(processHandle, address, null, symbol);
if (SymGetLineFromAddr64(processHandle, address, &displacement, &line)) {
if (string == null) {
print("\tat %s in %s: line: %lu: address %0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
} else {
string->appendf("\tat %s in %s: line: %lu: address %0x%0X\n", symbol->Name, line.FileName, line.LineNumber, symbol->Address);
}
} else {
if (string == null) {
print("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
print("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
} else {
string->appendf("\tSymGetLineFromAddr64 returned error code %lu.\n", GetLastError());
string->appendf("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address);
}
}
}
#undef BACKTRACE_MAX_FUNCTION_NAME_LENGTH
}
#else
// OSX and Linux stacktrace stuff.
#include <execinfo.h> // backtrace, backtrace_symbols
#include <cxxabi.h> // abi::__cxa_demangle
// if |string| is non-null, then the stack trace will be concatenated to it instead of being printed to stdout.
void trace(String* string) {
TYPES_H_FTAG;
void* stack[BACKTRACE_MAX_FRAMES];
u32 stackSize = backtrace(stack, BACKTRACE_MAX_FRAMES);
// resolve addresses into strings containing "filename(function+address)"
// this array must be free()-ed
char** traces = backtrace_symbols(stack, stackSize);
// iterate over the returned symbol lines. skip the first, it is the address of this function
for (u32 i = 1; i < stackSize; i++) {
// the names as provided by 'backtrace_symbols' are mangled for some reason.
// we have to demangle them, using this weird api
char buffer[1024];
const char* mangledNameBegin = String::firstCharOccurence(traces[i], '_');
const char* mangledNameEnd = String::lastCharOccurence(traces[i], '+');
if (mangledNameBegin == null || mangledNameEnd == null) {
// we can't demangle this name for some reason, just copy the mangled name to the buffer
size_t length = String::len(traces[i]);
String::memcpy(buffer, (void*)traces[i], length);
buffer[length] = '\0';
} else {
size_t length = mangledNameEnd - 1 - mangledNameBegin;
String::memcpy(buffer, (void*)mangledNameBegin, length);
buffer[length] = '\0';
}
s32 status = -1;
char* trace = abi::__cxa_demangle(buffer, null, null, &status);
if (trace == null) {
println("warning: failed to demangle name: %s", traces[i]);
continue;
}
if (string == null) {
print("%s\n", trace);
} else {
string->appendf("%s\n", trace);
}
}
pFree(traces);
}
#undef BACKTRACE_MAX_FRAMES
#endif
void _debug(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) {
print("%sdebug:%s null\n", ANSI_BLUE, ANSI_RESET);
return;
}
va_list args;
va_start(args, format);
print("%sdebug:%s ", ANSI_BLUE, ANSI_RESET);
vprintln(format, args);
va_end(args);
}
void _warn(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) {
print("%swarning:%s null\n", ANSI_YELLOW, ANSI_RESET);
return;
}
va_list args;
va_start(args, format);
print("%swarning:%s ", ANSI_YELLOW, ANSI_RESET);
vprintln(format, args);
va_end(args);
}
static void (*customDie)(const char* string) = null;
// if you want to override what happens by default when your program calls 'die', you can do so here.
// just keep in mind the intention is for 'die' to be for when your program has encountered a fatal, unrecoverable error.
void setCustomDieBehavior(void (*dieBehavior)(const char* string)) {
customDie = dieBehavior;
}
// for fatal errors which may occur at runtime, even on a release binary.
// if a fatal error should not occur at runtime on a release binary, consider preferring 'massert'
// it's unclear when you should use asserts vs. die actually. idk man
void die(const char* format, ...) {
TYPES_H_FTAG;
if (format == null) {
if (customDie == null) {
print("%serror:%s (unspecified error)\n", ANSI_RED, ANSI_RESET);
trace();
exit(1);
return;
} else {
String string = String128f("error: (unspecified error)\n");
trace(&string);
customDie(string.c_str());
return;
}
}
va_list args;
va_start(args, format);
if (customDie == null) {
println("%serror:%s", ANSI_RED, ANSI_RESET);
vprintln(format, args);
println();
va_end(args);
trace();
exit(1);
} else {
String string = String128f("");
string.setfv(format, args);
string.append("\n");
trace(&string);
va_end(args);
customDie(string.c_str());
}
}
void print(bool b) { TYPES_H_FTAG; print("%s", b ? "true" : "false"); }
void print(char c) { TYPES_H_FTAG; print("%c", c); }
void print(signed int i) { TYPES_H_FTAG; print("%d", i); }
void print(unsigned int i) { TYPES_H_FTAG; print("%u", i); }
void print(float f) { TYPES_H_FTAG; print("%.14g", f); }
void print(double d) { TYPES_H_FTAG; print("%.14g", d); }
void print(void* p) { TYPES_H_FTAG; print("%p", p); }
void print(char* s) { TYPES_H_FTAG; print("%s", s); }
#ifndef _WIN32
void print(size_t i) { TYPES_H_FTAG; print("%u", i); }
void println(size_t i) { TYPES_H_FTAG; print(i); print("\n"); }
#endif
void println(bool b) { TYPES_H_FTAG; print(b); print("\n"); }
void println(char c) { TYPES_H_FTAG; print(c); print("\n"); }
void println(signed int i) { TYPES_H_FTAG; print(i); print("\n"); }
void println(unsigned int i) { TYPES_H_FTAG; print(i); print("\n"); }
void println(float f) { TYPES_H_FTAG; print(f); print("\n"); }
void println(double d) { TYPES_H_FTAG; print(d); print("\n"); }
void println(void* p) { TYPES_H_FTAG; print(p); print("\n"); }
void println(char* s) { TYPES_H_FTAG; print(s); print("\n"); }
void println() { TYPES_H_FTAG; print("\n"); }
#ifdef _USING_GLM_TYPES__
void print(glm::vec<2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print("vec2: %.14g,%.14g", v.x, v.y); }
void print(glm::vec<3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print("vec3: %.14g,%.14g,%.14g", v.x, v.y, v.z); }
void print(glm::vec<4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print("vec4: %.14g,%.14g,%.14g,%.14g", v.x, v.y, v.z, v.w); }
void print(glm::mat<2, 2, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print("mat2: "); print(m[0]); print(m[1]); }
void print(glm::mat<3, 3, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print("mat3: "); print(m[0]); print(m[1]); print(m[2]); }
void print(glm::mat<4, 4, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print("mat4: "); print(m[0]); print(m[1]); print(m[2]); print(m[3]); }
void println(glm::vec<2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print(v); print("\n"); }
void println(glm::vec<3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print(v); print("\n"); }
void println(glm::vec<4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; print(v); print("\n"); }
void println(glm::mat<2, 2, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print(m); print("\n"); }
void println(glm::mat<3, 3, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print(m); print("\n"); }
void println(glm::mat<4, 4, float, (glm::qualifier) 3> m) { TYPES_H_FTAG; print(m); print("\n"); }
#endif

155
print.h

@ -0,0 +1,155 @@
#ifndef PRINT_H
#define PRINT_H
#include <stdarg.h> // va_list
#include "string.h"
#include "types.h"
#ifndef _WIN32
// comment this line out if you don't want terminal output to contain ANSI escape codes
// by default on windows executables it's disabled, since things like the command prompt still doesn't support ANSI color codes
#define PRINT_H_USE_ANSI_COLOR_ESCAPE_CODES
#endif
#ifdef PRINT_H_USE_ANSI_COLOR_ESCAPE_CODES
#define ANSI_BLACK "\x001B[30m"
#define ANSI_RED "\x001B[31m"
#define ANSI_GREEN "\x001B[32m"
#define ANSI_YELLOW "\x001B[33m"
#define ANSI_BLUE "\x001B[34m"
#define ANSI_PURPLE "\x001B[35m"
#define ANSI_CYAN "\x001B[36m"
#define ANSI_WHITE "\x001B[37m"
#define ANSI_BRIGHT_BLACK "\x001B[90m"
#define ANSI_BRIGHT_RED "\x001B[91m"
#define ANSI_BRIGHT_GREEN "\x001B[92m"
#define ANSI_BRIGHT_YELLOW "\x001B[93m"
#define ANSI_BRIGHT_BLUE "\x001B[94m"
#define ANSI_BRIGHT_MAGENTA "\x001B[95m"
#define ANSI_BRIGHT_CYAN "\x001B[96m"
#define ANSI_BRIGHT_WHITE "\x001B[97m"
#define ANSI_BLACK_BACKGROUND "\x001B[40m"
#define ANSI_RED_BACKGROUND "\x001B[41m"
#define ANSI_GREEN_BACKGROUND "\x001B[42m"
#define ANSI_YELLOW_BACKGROUND "\x001B[43m"
#define ANSI_BLUE_BACKGROUND "\x001B[44m"
#define ANSI_PURPLE_BACKGROUND "\x001B[45m"
#define ANSI_CYAN_BACKGROUND "\x001B[46m"
#define ANSI_WHITE_BACKGROUND "\x001B[47m"
#define ANSI_WRAP "\x001B[7m"
#define ANSI_BLINK "\x001B[5m"
#define ANSI_CLEAR "\x001B[2J\x001B[;H"
#define ANSI_RESET "\x001B[0m"
#else
#define ANSI_BLACK ""
#define ANSI_RED ""
#define ANSI_GREEN ""
#define ANSI_YELLOW ""
#define ANSI_BLUE ""
#define ANSI_PURPLE ""
#define ANSI_CYAN ""
#define ANSI_WHITE ""
#define ANSI_BRIGHT_BLACK ""
#define ANSI_BRIGHT_RED ""
#define ANSI_BRIGHT_GREEN ""
#define ANSI_BRIGHT_YELLOW ""
#define ANSI_BRIGHT_BLUE ""
#define ANSI_BRIGHT_MAGENTA ""
#define ANSI_BRIGHT_CYAN ""
#define ANSI_BRIGHT_WHITE ""
#define ANSI_BLACK_BACKGROUND ""
#define ANSI_RED_BACKGROUND ""
#define ANSI_GREEN_BACKGROUND ""
#define ANSI_YELLOW_BACKGROUND ""
#define ANSI_BLUE_BACKGROUND ""
#define ANSI_PURPLE_BACKGROUND ""
#define ANSI_CYAN_BACKGROUND ""
#define ANSI_WHITE_BACKGROUND ""
#define ANSI_WRAP ""
#define ANSI_BLINK ""
#define ANSI_CLEAR ""
#define ANSI_RESET ""
#endif
extern void vprint(const char* format, va_list args);
extern void vprintln(const char* format, va_list args);
extern void print(const char* format, ...);
extern void println(const char* format, ...);
// Prints a stack trace, or concatenates the stack trace to |string| if it is not null.
extern void trace(String* string = null);
// This ends the program and calls trace(). generally you should use 'massert' instead
extern void die(const char* format, ...);
// when calling 'die', instead of the default behavior,
// (print a stack trace and then call 'exit(1)')
// do something else.
//
// The function will get passed a string which is the formatted string you passed to 'die' + a stack trace.
extern void setCustomDieBehavior(void (*dieBehavior)(const char* string));
#ifdef PRINT_DEBUG
// simple wrapper to allow custom messages...
//#define massert(test, message) (((void)(message), test))
#define massert(test, message) if (!(test)) die("%s\n", message);
extern void _debug(const char* format, ...);
#define debug(format, ...) _debug(format, ##__VA_ARGS__)
// @NOTE there's a conflict on win32 with the name 'warning'...
extern void _warn(const char* format, ...);
#define warn(format, ...) _warn(format, ##__VA_ARGS__)
#else
// define some empty macros
#define massert(test, message) ((void) 0)
#define debug(format, ...)
#define warn(format, ...)
#endif
extern void print(bool b);
extern void print(char c);
extern void print(signed int i);
extern void print(unsigned int i);
extern void print(float f);
extern void print(double d);
extern void print(void* p);
extern void print(char* s);
#ifndef _WIN32
extern void print(size_t i);
extern void println(size_t i);
#endif
extern void println(bool b);
extern void println(char c);
extern void println(signed int i);
extern void println(unsigned int i);
extern void println(float f);
extern void println(double d);
extern void println(void* p);
extern void println(char* s);
extern void println();
#ifdef _USING_GLM_TYPES__
extern void print(glm::vec<2, float, (glm::qualifier) 3>);
extern void print(glm::vec<3, float, (glm::qualifier) 3>);
extern void print(glm::vec<4, float, (glm::qualifier) 3>);
extern void print(glm::mat<2, 2, float, (glm::qualifier) 3>);
extern void print(glm::mat<3, 3, float, (glm::qualifier) 3>);
extern void print(glm::mat<4, 4, float, (glm::qualifier) 3>);
extern void println(glm::vec<2, float, (glm::qualifier) 3> v);
extern void println(glm::vec<3, float, (glm::qualifier) 3> v);
extern void println(glm::vec<3, float, (glm::qualifier) 3> v);
extern void println(glm::mat<2, 2, float, (glm::qualifier) 3> m);
extern void println(glm::mat<3, 3, float, (glm::qualifier) 3> m);
extern void println(glm::mat<4, 4, float, (glm::qualifier) 3> m);
#endif
#endif

316
serialize.cpp

@ -0,0 +1,316 @@
#include <fast_float/fast_float.h>
#include "types.h"
#include "serialize.h"
#include "string.h"
#include "print.h"
static inline const char* getFormatStringOut(u8 v) { TYPES_H_FTAG; return "%hu\n"; }
static inline const char* getFormatStringOut(u16 v) { TYPES_H_FTAG; return "%hu\n"; }
static inline const char* getFormatStringOut(u32 v) { TYPES_H_FTAG; return "%u\n"; }
static inline const char* getFormatStringOut(u64 v) { TYPES_H_FTAG; return "%llu\n"; }
static inline const char* getFormatStringOut(s8 v) { TYPES_H_FTAG; return "%hd\n"; }
static inline const char* getFormatStringOut(s16 v) { TYPES_H_FTAG; return "%hd\n"; }
static inline const char* getFormatStringOut(s32 v) { TYPES_H_FTAG; return "%d\n"; }
static inline const char* getFormatStringOut(s64 v) { TYPES_H_FTAG; return "%lld\n"; }
static inline const char* getFormatStringOut(float v) { TYPES_H_FTAG; return "%f\n"; }
static inline const char* getFormatStringOut(double v) { TYPES_H_FTAG; return "%f\n"; }
// important constraint - strings need to be wrapped in double-quotes.
// the sentinel value 'null' without quotations is used to denote null values, which means
// if strings were not wrapped in double quotes, you would not be able to distinguish null
// values from the literal string "null".
static inline const char* getFormatStringOut(char* v) { TYPES_H_FTAG; return "\"%s\"\n"; }
static inline const char* getFormatStringOut(const char* v) { TYPES_H_FTAG; return "\"%s\"\n"; }
#ifdef _USING_GLM_TYPES__
static inline const char* getFormatStringOut(glm::vec<2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f\n"; }
static inline const char* getFormatStringOut(glm::vec<3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f\n"; }
static inline const char* getFormatStringOut(glm::vec<4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f\n"; }
static inline const char* getFormatStringOut(glm::mat<2, 2, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f\n"; }
static inline const char* getFormatStringOut(glm::mat<3, 3, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f %f %f %f %f %f\n"; }
static inline const char* getFormatStringOut(glm::mat<4, 4, float, (glm::qualifier) 3> v) { TYPES_H_FTAG; return "%f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f\n"; }
#endif
#define SERIALIZE_H_FUNC_BODY str->appendf(getFormatStringOut(v), v);
void serialize(String* str, u8 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, u16 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, u32 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, u64 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s8 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s16 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s32 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, s64 v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, float v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
void serialize(String* str, double v) { TYPES_H_FTAG; SERIALIZE_H_FUNC_BODY }
template<typename T> // do I really need a template for this?
static inline void deserializeInteger(char** buffer, T* v) {
TYPES_H_FTAG;
char* _buffer = *buffer;
T value = 0;
// skip leading whitespace - all our functions do this, so gotta
while (String::isAsciiWhitespace(*_buffer)) {
_buffer++;
}
// check the first character for a negative sign
bool negative = false;
if (_buffer[0] == '-') {
negative = true;
_buffer++;
}
// parse the digits of the number. doesn't account for overflow
while (String::isDigit(*_buffer)) {
value = 10 * value + (*_buffer++ - '0');
}
// make it negative if the first character was '-'
if (negative) value *= -1;
// store the value back into v
*v = value;
// if the original pointer is the same as the current, we didn't parse anything
massert(_buffer != *buffer, "tried to parse an integer, and found nothing");
// report back how far we advanced the buffer
*buffer = _buffer;
}
// just for integers, signed/unsigned including size_t
#define SERIALIZE_H_DESERIALIZE_FUNC_BODY deserializeInteger(buffer, v);
void deserialize(char** buffer, u8* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, u16* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, u32* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, u64* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s8* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s16* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s32* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
void deserialize(char** buffer, s64* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
// base value = ceil(log10(2^64)) or ceil(log10(2^32))
// is this right? lemire's paper mentions you only need 17 digits for 64 bit floats: https://lemire.me/en/publication/arxiv2101/
// +1 for 'e'
// +1 for '.'
// +1 for '^'
// +1 for '-'/'+'
static const u32 BINARY32_MAX_CHARS = 14;
static const u32 BINARY64_MAX_CHARS = 24;
void deserialize(char** buffer, float* v) {
TYPES_H_FTAG;
char* _buffer = *buffer;
while (String::isAsciiWhitespace(*_buffer)) _buffer++;
fast_float::from_chars_result result = fast_float::from_chars(_buffer, _buffer + BINARY32_MAX_CHARS, *v);
massert(result.ec == std::errc(), "failed to parse a float");
*buffer = (char*) result.ptr;
}
void deserialize(char** buffer, double* v) {
TYPES_H_FTAG;
char* _buffer = *buffer;
while (String::isAsciiWhitespace(*_buffer)) _buffer++;
fast_float::from_chars_result result = fast_float::from_chars(_buffer, _buffer + BINARY64_MAX_CHARS, *v);
massert(result.ec == std::errc(), "failed to parse a double");
*buffer = (char*) result.ptr;
}
#ifndef _WIN32
// win32 doesn't treat size_t as different than a u64, which causes ambiguous function calls
static inline const char* getFormatStringOut(size_t v) { TYPES_H_FTAG; return "%lu\n"; }
void serialize(String* str, size_t v) { SERIALIZE_H_FUNC_BODY }
void deserialize(char** buffer, size_t* v) { SERIALIZE_H_DESERIALIZE_FUNC_BODY }
#endif
// STRING STUFF
void serialize(String* str, char* v) {
TYPES_H_FTAG;
SERIALIZE_HANDLE_NULL(str, v);
SERIALIZE_H_FUNC_BODY;
}
void serialize(String* str, const char* v) {
TYPES_H_FTAG;
SERIALIZE_HANDLE_NULL(str, v);
SERIALIZE_H_FUNC_BODY;
}
#define SERIALIZE_SCRATCH_BUFFER_SIZE 2048
// make this number be the largest number of bytes you can expect to deserialize when deserializing a single string, in the case when the memory allocation must be done by the deserialization code.
// meaning, you don't have a pre-allocated buffer of a fixed, known size, like a char name[32] or something.
//
// if you call `deserialize(char** buffer, char* v)`, it's expected that you know what you're doing,
// and the |buffer| won't contain more characters than you can fit in |v|.
//
// in the case where you call `deserialize(char** buffer, char** v)`, it's assumed the size of the string
// is mostly unknown, but some buffer still must be allocated in order to be filled with the deserialized data.
// we use the SERIALIZE_SCRATCH_BUFFER for this duty, and Strcpy out of it once done to avoid wasting space.
//
// the ideal size of this buffer is use-case dependent, and you will have to change that yourself.
//
// there are asserts in the codepaths that use it for overflowing it, if you are unsure.
//
static char SERIALIZE_SCRATCH_BUFFER[SERIALIZE_SCRATCH_BUFFER_SIZE];
static s32 deserializeString(char** buffer, char* v, s32 vSize) {
TYPES_H_FTAG;
char* _buffer = *buffer;
while (String::isAsciiWhitespace(*_buffer)) _buffer++;
massert(_buffer[0] == '"', "expecting to deserialize a string, but found something other than a double quote");
_buffer++; // skip over the first quote
s32 i = 0;
while (_buffer[i] != '"') {
// @TODO handle escaped quotes?
v[i] = _buffer[i];
i++;
massert(i <= (vSize - 1), "deserializing a string bigger than the reported size of the buffer we are trying to fill!");
}
v[i] = '\0';
*buffer = _buffer + i + 1; // +i for the number of chars in string, +1 for final quotemark
return i;
}
static s32 deserializeString(char** buffer, char* v) {
TYPES_H_FTAG;
char* _buffer = *buffer;
while (String::isAsciiWhitespace(*_buffer)) _buffer++;
massert(_buffer[0] == '"', "expecting to deserialize a string, but found something other than a double quote");
_buffer++; // skip over the first quote
s32 i = 0;
while (_buffer[i] != '"') {
// @TODO handle escaped quotes?
v[i] = _buffer[i];
i++;
}
v[i] = '\0';
*buffer = _buffer + i + 1; // +i for the number of chars in string, +1 for final quotemark
return i;
}
void deserialize(char** buffer, char* v) {
TYPES_H_FTAG;
deserializeString(buffer, v);
}
void deserialize(char** buffer, const char* v) {
TYPES_H_FTAG;
deserializeString(buffer, (char*) v);
}
void deserialize(char** buffer, char** v) {
TYPES_H_FTAG;
DESERIALIZE_HANDLE_NULL(buffer, v);
s32 i = deserializeString(buffer, SERIALIZE_SCRATCH_BUFFER, SERIALIZE_SCRATCH_BUFFER_SIZE);
massert(i >= 0, "didn't deserialize anything when expecting a string!");
*v = String::cpy(SERIALIZE_SCRATCH_BUFFER, (u32) i);
}
void deserialize(char** buffer, const char** v) {
TYPES_H_FTAG;
DESERIALIZE_HANDLE_NULL(buffer, (char*) v); // error: readonly variable is not assignable
s32 i = deserializeString(buffer, SERIALIZE_SCRATCH_BUFFER, SERIALIZE_SCRATCH_BUFFER_SIZE);
massert(i >= 0, "didn't deserialize anything when expecting a string!");
*v = String::cpy(SERIALIZE_SCRATCH_BUFFER, (u32) i);
}
#ifdef _USING_GLM_TYPES__
// I have no fucking idea why, but the declarations of glm types here resolve to a type,
// that has a template parameter == 0, but all other instances of those types in my program
// have that template parameter == 3, so everything below becomes unresolved symbols if
// I don't do the nasty template garbage here
void serialize(String* str, glm::vec<2, float, (glm::qualifier) (glm::qualifier) 3> v) {
TYPES_H_FTAG;
str->appendf(getFormatStringOut(v), v[0], v[1]);
}
void serialize(String* str, glm::vec<3, float, (glm::qualifier) (glm::qualifier) 3> v) {
TYPES_H_FTAG;
str->appendf(getFormatStringOut(v), v[0], v[1], v[2]);
}
void serialize(String* str, glm::vec<4, float, (glm::qualifier) 3> v) {
TYPES_H_FTAG;
str->appendf(getFormatStringOut(v), v[0], v[1], v[2], v[3]);
}
void serialize(String* str, glm::mat<2, 2, float, (glm::qualifier) 3> v) {
TYPES_H_FTAG;
str->appendf(getFormatStringOut(v)
, v[0][0], v[0][1]
, v[1][0], v[1][1]);
}
void serialize(String* str, glm::mat<3, 3, float, (glm::qualifier) 3> v) {
TYPES_H_FTAG;
str->appendf(getFormatStringOut(v)
, v[0][0], v[0][1], v[0][2]
, v[1][0], v[1][1], v[1][2]
, v[2][0], v[2][1], v[2][2]);
}
void serialize(String* str, glm::mat<4, 4, float, (glm::qualifier) 3> v) {
TYPES_H_FTAG;
str->appendf(getFormatStringOut(v)
, v[0][0], v[0][1], v[0][2], v[0][3]
, v[1][0], v[1][1], v[1][2], v[1][3]
, v[2][0], v[2][1], v[2][2], v[2][3]
, v[3][0], v[3][1], v[3][2], v[3][3]);
}
void deserialize(char** buffer, glm::vec<2, float, (glm::qualifier) 3>* v) {
TYPES_H_FTAG;
float* _v = (float*) v;
for (u32 i = 0; i < 2; i++) {
deserialize(buffer, _v + i);
}
}
void deserialize(char** buffer, glm::vec<3, float, (glm::qualifier) 3>* v) {
TYPES_H_FTAG;
float* _v = (float*) v;
for (u32 i = 0; i < 3; i++) {
deserialize(buffer, _v + i);
}
}
void deserialize(char** buffer, glm::vec<4, float, (glm::qualifier) 3>* v) {
TYPES_H_FTAG;
float* _v = (float*) v;
for (u32 i = 0; i < 4; i++) {
deserialize(buffer, _v + i);
}
}
void deserialize(char** buffer, glm::mat<2, 2, float, (glm::qualifier) 3>* v) {
TYPES_H_FTAG;
float* m = (float*) v;
for (u32 i = 0; i < 4; i++) {
deserialize(buffer, m + i);
}
}
void deserialize(char** buffer, glm::mat<3, 3, float, (glm::qualifier) 3>* v) {
TYPES_H_FTAG;
float* m = (float*) v;
for (u32 i = 0; i < 9; i++) {
deserialize(buffer, m + i);
}
}
void deserialize(char** buffer, glm::mat<4, 4, float, (glm::qualifier) 3>* v) {
TYPES_H_FTAG;
float* m = (float*) v;
for (u32 i = 0; i < 16; i++) {
deserialize(buffer, m + i);
}
}
#undef SERIALIZE_H_FUNC_BODY
#undef SERIALIZE_H_DESERIALIZE_FUNC_BODY
#endif

208
serialize.h

@ -0,0 +1,208 @@
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include "print.h"
#include "types.h"
#include "string.h"
/*
NOTES ON SERIALIZATION
after wrestling with various reflection libraries for a week, I decided to use none of them.
there are two-ish ways to do serialization:
A. write or use a 'reflection' system which is able to introspect types in the program, and generate the code to serialize/deserialize using this introspection
B. define functions manually which do the work, and modify them whenever the underlying data structures change
you can do a mixture of option A and option B. We lean towards option B.
primitive types used in the program, (defined in types.h) will automatically know how to serialize and deserialize themselves.
this can optionally include our String type, and/or vector, matrix and quaternion types (default glm).
for most structs, there is a bit more elbow grease required.
define a function with this signature:
// serialize a T
void serialize(String* str, T v);
and one with this signature:
// serialize a pointer to T
void serialize(String* str, T* v);
and for deserialization:
// in this case, we assume the address |v| has been allocated, and we fill in the slots
void deserialize(char** buffer, T* v);
// in this case, we're deserializing a pointer, so we have to allocate memory ourselves.
// that's up to the implementer to do correctly.
// it's possible to deserialize 'null' here
void deserialize(char** buffer, T** v);
you have to manually implement these functions and change them when the struct members change.
the body of the serialize function bodies will essentially call 'serialize' on each of the members you
wish to serialize. if you want to skip certain members, just do not call 'serialize' on them.
similarly, for deserialize you should call deserialize on each member.
the order matters. if you wish to be able deserialize something you have serialized, the order which you process
members must be the same between the serialization/deserialization calls.
that is essentially it, with some caveats in the circumstance where you could be serializing/deserializing a NULL,
and in the case where you are deserializing a pointer to a data structure (you have to perform allocation in this case).
these cases are explained below.
*/
// in an #include-guarded header you can use this helper macro to define a few function stubs
// + potentially helpful function definitions, with no templates required.
//
// having serialization allows trivial/naive implementations of things like cloning and equality.
// these definitions can be done with macros, provided you have written an implementation of |serialize|
// and |deserialize| for your data type.
//
#define SERIALIZE_H_HELPER_FUNCTION_DEFS(T) \
extern void serialize(String* str, T* v); \
extern void serialize(String* str, T v); \
extern void deserialize(char** buffer, T* v); \
extern void deserialize(char** buffer, T** v); \
static void serializePrint(T* v) { \
TYPES_H_FTAG; \
String str = String(""); \
serialize(&str, v); \
println(str.c_str()); \
} \
static bool serializeEquals(T* t1, T* t2) { \
TYPES_H_FTAG; \
String s1 = String128f(""); \
String s2 = String128f(""); \
serialize(&s1, t1); \
serialize(&s2, t2); \
return String::eq(s1.c_str(), s2.c_str()); \
}
// if you implement deserialize with a T*.
#define SERIALIZE_H_HELPER_CLONE_T_POINTER(T) \
static void serializeClone(T* orig, T* destination) { \
TYPES_H_FTAG; \
String str = String128f(""); \
serialize(&str, orig); \
char* buffer = str.c_str(); \
deserialize(&buffer, destination); \
}
// if you implement deserialize with a T**.
#define SERIALIZE_H_HELPER_CLONE_T_DOUBLE_POINTER(T) \
static void serializeClone(T* orig, T** destination) { \
TYPES_H_FTAG; \
String str = String128f(""); \
serialize(&str, orig); \
char* buffer = str.c_str(); \
deserialize(&buffer, destination); \
}
// The body of the serialize function should simply call `serialize(str, member)` for each member of the struct that you wish to serialize.
// If the struct is nullable (pointer to struct), you have to check for that. this helper will do that for you:
#define SERIALIZE_NULL_SENTINEL "null\n"
#define SERIALIZE_HANDLE_NULL(str, pointer) \
if (pointer == null) { \
str->append(SERIALIZE_NULL_SENTINEL); \
return; \
}
// Similarly, for deserializing pointers, you need to check for our null sentinel value first
// make sure to write your serialized data out in binary mode if you use fwrite, otherwise you're likely
// to run into an annoying bug where the '\n' in the null sentinel will be converted to '\r\n' on windows,
// causing all null sentinels to be ignored (the in-memory "null\n" will never be equal to the on-disk "null\r\n",
// obviously)
#define DESERIALIZE_HANDLE_NULL(buffer, pointer) \
char* _buffer = *buffer; \
while (String::isAsciiWhitespace(*_buffer)) _buffer++; \
if (String::memeq((unsigned char*)_buffer, (unsigned char*)SERIALIZE_NULL_SENTINEL, (sizeof(SERIALIZE_NULL_SENTINEL) - 1))) { \
*pointer = null; \
*buffer += (sizeof(SERIALIZE_NULL_SENTINEL) - 1); \
return; \
}
//
// Our HashTable implementation (Table.hpp) and Array implementation (Array.hpp) knows how to serialize/deserialize, but
// for your own custom containers or standard containers you'll have to define serialize/deserialize for them.
//
// What our system does is basically, for serialization:
// - the code that wants to serialize something initializes a String type which has an interface that lets you concat stuff to it.
// - call 'serialize' on the thing you want to serialize, passing in this String type as well
// - the behavior is defined by you from this point, but probably you recursively call 'serialize' on members of the type you're serializing, and data just gets concatenated to the string as you go.
// at the end the string will have the serialized data.
//
// For deserialization:
// - get some big buffer of serialized data, probably from reading a file, as a char*.
// - define a variable of the type you want to initialize from this serialized data
// - pass the address of the buffer (type char**) and the address of the variable you want to initialize to 'deserialize'
// - behavior at this point is defined by you, but probably you recursively call 'deserialize' on the members of the type you are initializing.
// - at each step, the code will essentially assert that data of a particular type exists in serialized form at the address |buffer| points to, and try to read that data from that address. if it succeeds, it will increment the buffer pointer by the number of characters read, so it should be pointing at the next data member if it exists, or pointing to the null character if it's the end of the stream. if it ever finds something unexpected, it will crash.
// - IMPORTANT: you have to know the order of the data in the serialized form. You cannot try to deserialize out of order.
// - if you want to skip certain members, either don't serialize them at all, or deserialize them into a dummy address.
//
extern void serialize(String* str, u8 v);
extern void serialize(String* str, u16 v);
extern void serialize(String* str, u32 v);
extern void serialize(String* str, u64 v);
extern void serialize(String* str, s8 v);
extern void serialize(String* str, s16 v);
extern void serialize(String* str, s32 v);
extern void serialize(String* str, s64 v);
extern void serialize(String* str, float v);
extern void serialize(String* str, double v);
extern void deserialize(char** buffer, u8* v);
extern void deserialize(char** buffer, u16* v);
extern void deserialize(char** buffer, u32* v);
extern void deserialize(char** buffer, u64* v);
extern void deserialize(char** buffer, s8* v);
extern void deserialize(char** buffer, s16* v);
extern void deserialize(char** buffer, s32* v);
extern void deserialize(char** buffer, s64* v);
extern void deserialize(char** buffer, float* v);
extern void deserialize(char** buffer, double* v);
// these serialize and deserialize strings, not single characters.
extern void serialize(String* str, char* v);
extern void serialize(String* str, const char* v);
// read the string notes above in the large block comment for gotchas involving these functions.
extern void deserialize(char** buffer, char* v);
extern void deserialize(char** buffer, const char* v);
extern void deserialize(char** buffer, char** v);
extern void deserialize(char** buffer, const char** v);
#ifndef _WIN32
extern void serialize(String* str, size_t v);
extern void deserialize(char** buffer, size_t* v);
#endif
#ifdef _USING_GLM_TYPES__
extern void serialize(String* str, glm::vec<2, float, (glm::qualifier) 3> v);
extern void serialize(String* str, glm::vec<3, float, (glm::qualifier) 3> v);
extern void serialize(String* str, glm::vec<4, float, (glm::qualifier) 3> v);
extern void serialize(String* str, glm::mat<2, 2, float, (glm::qualifier) 3> v);
extern void serialize(String* str, glm::mat<3, 3, float, (glm::qualifier) 3> v);
extern void serialize(String* str, glm::mat<4, 4, float, (glm::qualifier) 3> v);
extern void deserialize(char** buffer, glm::vec<2, float, (glm::qualifier) 3>* v);
extern void deserialize(char** buffer, glm::vec<3, float, (glm::qualifier) 3>* v);
extern void deserialize(char** buffer, glm::vec<4, float, (glm::qualifier) 3>* v);
extern void deserialize(char** buffer, glm::mat<2, 2, float, (glm::qualifier) 3>* v);
extern void deserialize(char** buffer, glm::mat<3, 3, float, (glm::qualifier) 3>* v);
extern void deserialize(char** buffer, glm::mat<4, 4, float, (glm::qualifier) 3>* v);
#endif
#endif

50
signal-handler.h

@ -0,0 +1,50 @@
#ifndef SIGNAL_HANDLER_H
#define SIGNAL_HANDLER_H
#include <signal.h> // for signal() and the SIG macros
#include "types.h"
#include "print.h"
// the running process can receive and respond to a variety of platform-dependent 'signals' during runtime from the OS.
// freebsd has something like 30 signals, windows has a subset, just 6. we'll just deal with 6.
static inline void defaultHandler(s32 signal) {
TYPES_H_FTAG;
switch (signal) {
case SIGSEGV:
case SIGABRT:
print("%serror%s: %d\n", ANSI_RED, ANSI_RESET, signal);
trace();
case SIGINT:
die("Bye! (SIGINT)\n");
case SIGTERM:
die(" We were asked to stop (SIGTERM).\n");
case SIGFPE:
die(" (SIGFPE).\n");
case SIGILL:
die(" (SIGILL).\n");
default:
die(" unknown/non-standard signal raised: %d\n", signal);
break;
}
}
static void setSignalHandlers(void(*handler)(s32 signal) = defaultHandler) {
TYPES_H_FTAG;
if (signal(SIGSEGV, handler) == SIG_ERR) die("failed to set SIGSEGV handler... zzz...\n");
if (signal(SIGABRT, handler) == SIG_ERR) die("failed to set SIGABRT handler... zzz...\n");
if (signal(SIGFPE, handler) == SIG_ERR) die("failed to set SIGFPE handler... zzz...\n");
if (signal(SIGILL, handler) == SIG_ERR) die("failed to set SIGILL handler... zzz...\n"); // does this fire on using sprintf (on Catalina)?
if (signal(SIGINT, handler) == SIG_ERR) die("failed to set SIGINT handler... zzz...\n");
if (signal(SIGTERM, handler) == SIG_ERR) die("failed to set SIGTERM handler... zzz...\n");
}
#endif

979
string.h

@ -0,0 +1,979 @@
#ifndef STRING_H
#define STRING_H
#include "types.h"
#include "alloc.h"
#include <string.h> // @TODO remove this
#define STB_SPRINTF_IMPLEMENTATION
#define STB_SPRINTF_STATIC
#include <stb/stb_sprintf.h>
#define STR_MEMALLOC pMalloc
#define STR_MEMFREE pFree
#define STR_ASSERT assert
#define STR_IMPLEMENTATION
#define STR_SUPPORT_STD_STRING 0
#define STR_DEFINE_STR32 0 // the type Str32, which would normally be available, conflicts with a type in MacTypes.h
// 'String' is a datatype, but it also is a namespace for a bunch of static 'char*' operations that
// you would normally find in the <cstring> or <string.h> header
class String {
public:
// Static empty buffer we can point to for empty strings
// Pointing to a literal increases the like-hood of getting a crash if someone attempts to write in the empty string buffer.
constexpr static char* EmptyBuffer = (char*) "\0NULL";
constexpr static unsigned char ASCII_LOWER[128] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100,101,102,103,104,105,106,107,108,109,110,111,
112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127
};
constexpr static unsigned char ASCII_UPPER[128] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,123,124,125,126,127
};
static inline s32 sprintf(char* buffer, const char* format, ...) {
TYPES_H_FTAG;
va_list args;
va_start(args, format);
s32 code = stbsp_vsprintf(buffer, format, args);
va_end(args);
return code;
}
static inline s32 snprintf(char* buffer, s32 count, const char* format, ...) {
TYPES_H_FTAG;
va_list args;
va_start(args, format);
s32 code = stbsp_vsnprintf(buffer, count, format, args);
va_end(args);
return code;
}
static inline s32 vsnprintf(char* buffer, int count, const char* format, va_list args) {
return stbsp_vsnprintf(buffer, count, format, args);
}
static inline bool isDigit(char c) {
TYPES_H_FTAG;
return (c >= '0') && (c <= '9');
}
static inline bool isAlpha(char c) {
TYPES_H_FTAG;
return (c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z');
}
static inline bool isHexDigit(char c) {
TYPES_H_FTAG;
return ((c >= '0') && (c <= '9'))
|| ((c >= 'A') && (c <= 'F'))
|| ((c >= 'a') && (c <= 'f'));
}
static inline bool isOctalDigit(char c) {
TYPES_H_FTAG;
return (c >= '0') && (c <= '7');
}
static inline bool isBinaryDigit(char c) {
TYPES_H_FTAG;
return c == '0' || c == '1';
}
static inline char* intToString(u64 integer) {
TYPES_H_FTAG;
u32 capacity = 10;
u32* remainders = (u32*) pMalloc(sizeof (u32) * capacity);
u32 count = 0;
while (true) {
if (capacity <= count) {
capacity *= 2;
remainders = (u32*) pRealloc(remainders, sizeof (u32) * capacity);
}
remainders[count++] = integer % 10;
integer /= 10;
if (integer == 0) break;
}
char* buffer = (char*) pMalloc(sizeof (char) * count + 1);
for (u32 i = 0; i < count; i++) {
buffer[count - i - 1] = '0' + remainders[i];
}
buffer[count] = '\0';
pFree(remainders);
return buffer;
}
static inline u64 hexStringToInt(const char* str) {
TYPES_H_FTAG;
u64 out = 0;
while (*str != '\0') {
u8 byte = *str++;
if ((byte >= '0') && (byte <= '9')) {
byte = byte - '0';
} else if ((byte >= 'a') && (byte <= 'f')) {
byte = byte - 'a' + 10;
} else if ((byte >= 'A') && (byte <= 'F')) {
byte = byte - 'A' + 10;
}
// only use the last four bits - precision of a single hex digit
out = (out << 4) | (byte & 0xF);
}
return out;
}
static inline u32 len(const char* string) {
TYPES_H_FTAG;
const char* start = string;
while (*string++ != '\0') {}
return (u32) (string - start);
}
// returns true if null-terminated strings |s1| and |s2| are equal
static inline bool eq(const char* s1, const char* s2) {
TYPES_H_FTAG;
u32 l1 = String::len(s1);
u32 l2 = String::len(s2);
if (l1 != l2) return false;
for (u32 i = 0; i < l1; i++) {
if (s1[i] != s2[i]) {
return false;
}
}
return true;
}
// same as |eq|, but handles |s1| and/or |s2| being null
static inline bool eqNullCheck(const char* s1, const char* s2) {
TYPES_H_FTAG;
if (s1 == null) {
if (s2 == null) {
return true;
} else {
return false;
}
} else if (s2 == null) {
return false;
}
return String::eq(s1, s2);
}
// heap allocates a copy of |string| and returns a pointer to it.
static inline char* cpy(const char* string, u32 length, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
char* buffer = (char*) allocator->mallocate(sizeof (char) * (length + 1), allocator->state);
u32 i = 0;
for (; i < length; i++) {
buffer[i] = string[i];
}
buffer[i] = '\0';
return buffer;
}
// heap allocates a copy of |string| and returns a pointer to it.
static inline char* cpy(const char* string, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
u32 len = String::len(string);
return String::cpy(string, len, allocator = Allocator::GetDefault());
}
static inline bool memeq(const unsigned char* m1, const unsigned char* m2, size_t length) {
TYPES_H_FTAG;
return memcmp(m1, m2, length) == 0;
}
static inline bool memeq(const unsigned char* m1, size_t l1, const unsigned char* m2, size_t l2) {
TYPES_H_FTAG;
if (l1 != l2) return false;
return memeq(m1, m2, l1);
}
#ifdef _WIN32
static inline size_t wcharToChar(wchar_t* wstring, char* buffer, size_t maxBufferLength) {
TYPES_H_FTAG;
return wcstombs(buffer, wstring, maxBufferLength);
}
#endif
static inline void* memset(void* p, char c, u32 length) {
TYPES_H_FTAG;
//__stosb((unsigned char*) p, c, length);
char* a = (char*) p;
for (u32 i = 0; i < length; i++) a[i] = c;
return a;
}
static inline void memcpy(void* dest, void* src, u32 size) {
TYPES_H_FTAG;
u8* dest_ = (u8*) dest;
u8* src_ = (u8*) src;
for (u32 i = 0; i < size; i++) {
dest_[i] = src_[i];
}
}
// replace all instances of |c1| in |string| with |c2|
static inline void replaceC(char* string, u32 length, char c1, char c2) {
TYPES_H_FTAG;
for (u32 i = 0; i < length; i++) {
if (string[i] == c1) {
string[i] = c2;
}
}
}
static inline const char* firstCharOccurence(const char* string, u32 length, char c) {
TYPES_H_FTAG;
for (s32 i = 0; i < length; i++) {
const char* s = string + i;
if (*s == c) {
return s;
}
}
return null;
}
static inline const char* firstCharOccurence(const char* string, char c) {
TYPES_H_FTAG;
return String::firstCharOccurence(string, String::len(string), c);
}
static inline const char* lastCharOccurence(const char* string, u32 length, char c) {
TYPES_H_FTAG;
for (s32 i = length - 1; i >= 0; i--) { // @NOTE 'i' needs to be a signed int here...
if (*(string + i) == c) {
return string + i;
}
}
return null;
}
static inline const char* lastCharOccurence(const char* string, char c) {
TYPES_H_FTAG;
return String::lastCharOccurence(string, String::len(string), c);
}
static inline bool hasSuffix(const char* string, const char* suffix) {
TYPES_H_FTAG;
const char* p = String::lastCharOccurence(string, String::len(string), suffix[0]);
if (p) return String::eq(p, suffix);
return false;
}
static inline u32 countLines(const char* buffer) {
TYPES_H_FTAG;
u32 lines = 0;
char c;
while ((c = *buffer) != '\0') {
if (c == '\n') lines++;
buffer++;
}
return lines;
}
static inline bool isAscii(const char* buffer, u32 length) {
TYPES_H_FTAG;
const unsigned char* ubuffer = (const unsigned char*) buffer;
for (u32 i = 0; i < length; i++) {
if (ubuffer[i] & 128) { // binary: 0b 1000 0000
return false;
}
}
return true;
}
static inline bool isAsciiWhitespace(char c) {
TYPES_H_FTAG;
switch (c) {
//case '\b':
//case '\v':
//case '\f':
case '\r':
case '\t':
case '\n':
case ' ':
return true;
default:
return false;
}
}
// static inline bool isUnicodeSpaceSeparator(wide character);
// @TODO ALL OF THESE TRIMS
//static inline char* trimStart(const char* str, u32 count);
//static inline char* trimEnd(const char* str, u32 count);
static inline char* trim(const char* str, u32 count, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
u32 length = String::len(str);
if (length <= count) {
return (char*) "";
}
char* buffer = (char*) allocator->mallocate(sizeof (char) * (length - 1), allocator->state);
u32 i = 0;
for (; i < (length - count); i++) {
buffer[i] = str[i + 1];
}
buffer[i] = '\0';
return buffer;
}
static inline char* asciiToLower(const char* str, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
u32 length = String::len(str);
char* buffer = (char*) allocator->mallocate(sizeof (char) * length + 1, allocator->state);
u32 i = 0;
for (; i < length; i++) {
buffer[i] = String::ASCII_LOWER[str[i]];
}
buffer[i] = '\0';
return buffer;
}
static inline char* asciiToUpper(const char* str, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
u32 length = String::len(str);
char* buffer = (char*) allocator->mallocate(sizeof (char) * length + 1, allocator->state);
u32 i = 0;
for (; i < length; i++) {
buffer[i] = String::ASCII_LOWER[str[i]];
}
buffer[i] = '\0';
return buffer;
}
static inline char* concat(const char* str1, const char* str2, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
u32 l1 = String::len(str1);
u32 l2 = String::len(str2);
u32 newLength = l1 + l2;
char* newBuffer = (char*) allocator->mallocate(sizeof (char) * newLength + 1, allocator->state);
u32 i = 0;
for (; i < newLength; i++) {
if (i < l1) {
newBuffer[i] = str1[i];
} else {
newBuffer[i] = str2[i - l1];
}
}
newBuffer[i] = '\0';
return newBuffer;
}
static inline u32 write(char* dest, const char* src, u32 length) {
TYPES_H_FTAG;
u32 i = 0;
for (; i < length; i++) {
dest[i] = src[i];
}
dest[i] = '\0';
return i;
}
// returns the number of characters written.
static inline u32 write(char* dest, const char* src) {
TYPES_H_FTAG;
u32 length = String::len(src);
return String::write(dest, src, length);
}
static inline char* read(const char* buffer, u32 length, Allocator* allocator = Allocator::GetDefault()) {
TYPES_H_FTAG;
char* tk = (char*) allocator->mallocate(sizeof (char) * length + 1, allocator->state);
u32 i = 0;
while (i < length) {
tk[i] = *(buffer + i);
i++;
}
tk[i] = '\0';
return tk;
}
char* Data; // Point to LocalBuf() or heap allocated
int Capacity : 21; // Max 2 MB
int LocalBufSize : 10; // Max 1023 bytes
unsigned int Owned : 1; // Set when we have ownership of the pointed data (most common, unless using set_ref() method or StringRef constructor)
inline char* c_str() { TYPES_H_FTAG; return Data; }
inline const char* c_str() const { TYPES_H_FTAG; return Data; }
inline bool empty() const { TYPES_H_FTAG; return Data[0] == 0; }
inline int length() const { TYPES_H_FTAG; return (int)strlen(Data); } // by design, allow user to write into the buffer at any time
inline int capacity() const { TYPES_H_FTAG; return Capacity; }
inline bool owned() const { TYPES_H_FTAG; return Owned ? true : false; }
inline char& operator[](size_t i) { TYPES_H_FTAG; return Data[i]; }
inline char operator[](size_t i) const { TYPES_H_FTAG; return Data[i]; }
inline String& operator=(const String& rhs) { TYPES_H_FTAG; set(rhs); return *this; }
inline bool operator==(const String& rhs) const { TYPES_H_FTAG; return strcmp(c_str(), rhs.c_str()) == 0; }
inline String& operator=(const char* rhs) { TYPES_H_FTAG; set(rhs); return *this; }
inline bool operator==(const char* rhs) const { TYPES_H_FTAG; return strcmp(c_str(), rhs) == 0; }
inline String() {
TYPES_H_FTAG;
Data = EmptyBuffer; // Shared READ-ONLY initial buffer for 0 capacity
Capacity = 0;
LocalBufSize = 0;
Owned = 0;
}
inline String(const String& rhs) : String() {
TYPES_H_FTAG;
set(rhs);
}
inline String(const char* rhs) : String() {
TYPES_H_FTAG;
set(rhs);
}
inline void set_ref(const char* src) {
TYPES_H_FTAG;
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
Data = src ? (char*)src : EmptyBuffer;
Capacity = 0;
Owned = 0;
}
inline void set(const String& src) {
TYPES_H_FTAG;
int buf_len = (int)strlen(src.c_str())+1;
if ((int)Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, (void*)src.c_str(), (size_t)buf_len);
Owned = 1;
}
inline void set(const char* src) {
TYPES_H_FTAG;
// We allow set(NULL) or via = operator to clear the string.
if (src == NULL)
{
clear();
return;
}
int buf_len = (int)strlen(src)+1;
if (Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, (void*)src, (size_t)buf_len);
Owned = 1;
}
inline void set(const char* src, const char* src_end) {
TYPES_H_FTAG;
STR_ASSERT(src != NULL && src_end >= src);
int buf_len = (int)(src_end-src)+1;
if ((int)Capacity < buf_len)
reserve_discard(buf_len);
memcpy(Data, (void*)src, (size_t)(buf_len - 1));
Data[buf_len-1] = 0;
Owned = 1;
}
// Clear
inline void clear() {
TYPES_H_FTAG;
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
if (LocalBufSize) {
Data = local_buf();
Data[0] = '\0';
Capacity = LocalBufSize;
Owned = 1;
} else {
Data = EmptyBuffer;
Capacity = 0;
Owned = 0;
}
}
// Reserve memory, preserving the current of the buffer
inline void reserve(int new_capacity) {
TYPES_H_FTAG;
if (new_capacity <= Capacity)
return;
char* new_data;
if (new_capacity < LocalBufSize) {
// Disowned -> LocalBuf
new_data = local_buf();
new_capacity = LocalBufSize;
} else {
// Disowned or LocalBuf -> Heap
new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char));
}
// string in Data might be longer than new_capacity if it wasn't owned, don't copy too much
#ifdef _MSC_VER
strncpy_s(new_data, (size_t)new_capacity, Data, (size_t)new_capacity - 1);
#else
strncpy(new_data, Data, (size_t)new_capacity - 1);
#endif
new_data[new_capacity - 1] = 0;
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
Data = new_data;
Capacity = new_capacity;
Owned = 1;
}
// Reserve memory, discarding the current of the buffer (if we expect to be fully rewritten)
inline void reserve_discard(int new_capacity) {
TYPES_H_FTAG;
if (new_capacity <= Capacity)
return;
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
if (new_capacity < LocalBufSize) {
// Disowned -> LocalBuf
Data = local_buf();
Capacity = LocalBufSize;
} else {
// Disowned or LocalBuf -> Heap
Data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char));
Capacity = new_capacity;
}
Owned = 1;
}
inline void shrink_to_fit() {
TYPES_H_FTAG;
if (!Owned || is_using_local_buf()) return;
int new_capacity = length() + 1;
if (Capacity <= new_capacity) return;
char* new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char));
memcpy(new_data, Data, (size_t)new_capacity);
STR_MEMFREE(Data);
Data = new_data;
Capacity = new_capacity;
}
// FIXME: merge setfv() and appendfv()?
inline int setfv(const char* fmt, va_list args) {
TYPES_H_FTAG;
// Needed for portability on platforms where va_list are passed by reference and modified by functions
va_list args2;
va_copy(args2, args);
// First try
int len = vsnprintf(Owned ? Data : NULL, Owned ? (size_t)Capacity : 0, fmt, args);
STR_ASSERT(len >= 0);
if (Capacity < len + 1)
{
reserve_discard(len + 1);
len = vsnprintf(Data, (size_t)len + 1, fmt, args2);
}
STR_ASSERT(Owned);
return len;
}
inline int setf(const char* fmt, ...) {
TYPES_H_FTAG;
va_list args;
va_start(args, fmt);
int len = setfv(fmt, args);
va_end(args);
return len;
}
inline int setfv_nogrow(const char* fmt, va_list args) {
TYPES_H_FTAG;
STR_ASSERT(Owned);
if (Capacity == 0) return 0;
int w = vsnprintf(Data, (size_t)Capacity, fmt, args);
Data[Capacity - 1] = 0;
Owned = 1;
return (w == -1) ? Capacity - 1 : w;
}
inline int setf_nogrow(const char* fmt, ...) {
TYPES_H_FTAG;
va_list args;
va_start(args, fmt);
int len = setfv_nogrow(fmt, args);
va_end(args);
return len;
}
inline int append_from(int idx, char c) {
TYPES_H_FTAG;
int add_len = 1;
if (Capacity < idx + add_len + 1)
reserve(idx + add_len + 1);
Data[idx] = c;
Data[idx + add_len] = 0;
STR_ASSERT(Owned);
return add_len;
}
inline int append_from(int idx, const char* s, const char* s_end) {
TYPES_H_FTAG;
if (!s_end) s_end = s + strlen(s);
int add_len = (int)(s_end - s);
if (Capacity < idx + add_len + 1) reserve(idx + add_len + 1);
memcpy(Data + idx, (void*)s, (size_t)add_len);
Data[idx + add_len] = 0; // Our source data isn't necessarily zero-terminated
STR_ASSERT(Owned);
return add_len;
}
// FIXME: merge setfv() and appendfv()?
inline int appendfv_from(int idx, const char* fmt, va_list args) {
TYPES_H_FTAG;
// Needed for portability on platforms where va_list are passed by reference and modified by functions
va_list args2;
va_copy(args2, args);
// First try
int add_len = vsnprintf(Owned ? Data + idx : NULL, Owned ? (size_t)(Capacity - idx) : 0, fmt, args);
STR_ASSERT(add_len >= 0);
if (Capacity < idx + add_len + 1) {
reserve(idx + add_len + 1);
add_len = vsnprintf(Data + idx, (size_t)add_len + 1, fmt, args2);
}
STR_ASSERT(Owned);
return add_len;
}
inline int appendf_from(int idx, const char* fmt, ...) {
TYPES_H_FTAG;
va_list args;
va_start(args, fmt);
int len = appendfv_from(idx, fmt, args);
va_end(args);
return len;
}
inline int append(char c) {
TYPES_H_FTAG;
int cur_len = length();
return append_from(cur_len, c);
}
inline int append(const char* s, const char* s_end = null) {
TYPES_H_FTAG;
int cur_len = length();
return append_from(cur_len, s, s_end);
}
inline int appendfv(const char* fmt, va_list args) {
TYPES_H_FTAG;
int cur_len = length();
return appendfv_from(cur_len, fmt, args);
}
int appendf(const char* fmt, ...) {
TYPES_H_FTAG;
va_list args;
va_start(args, fmt);
int len = appendfv(fmt, args);
va_end(args);
return len;
}
// Destructor for all variants
inline ~String()
{
if (Owned && !is_using_local_buf())
STR_MEMFREE(Data);
}
protected:
inline char* local_buf() { TYPES_H_FTAG; return (char*)this + sizeof(String); }
inline const char* local_buf() const { TYPES_H_FTAG; return (char*)this + sizeof(String); }
inline bool is_using_local_buf() const { TYPES_H_FTAG; return Data == local_buf() && LocalBufSize != 0; }
// Constructor for StringXXX variants with local buffer
String(unsigned short local_buf_size) {
TYPES_H_FTAG;
STR_ASSERT(local_buf_size < 1024);
Data = local_buf();
Data[0] = '\0';
Capacity = local_buf_size;
LocalBufSize = local_buf_size;
Owned = 1;
}
};
// Literal/reference string
class StringRef : public String {
public:
StringRef(const char* s) : String() { TYPES_H_FTAG; set_ref(s); }
};
// Types embedding a local buffer
// NB: we need to override the constructor and = operator for both String& and TYPENAME (without the later compiler will call a default copy operator)
#define STR_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
class TYPENAME : public String \
{ \
char local_buf[LOCALBUFSIZE]; \
public: \
TYPENAME() : String(LOCALBUFSIZE) {} \
TYPENAME(const String& rhs) : String(LOCALBUFSIZE) { TYPES_H_FTAG; set(rhs); } \
TYPENAME(const char* rhs) : String(LOCALBUFSIZE) { TYPES_H_FTAG; set(rhs); } \
TYPENAME(const TYPENAME& rhs) : String(LOCALBUFSIZE) { TYPES_H_FTAG; set(rhs); } \
TYPENAME& operator=(const char* rhs) { TYPES_H_FTAG; set(rhs); return *this; } \
TYPENAME& operator=(const String& rhs) { TYPES_H_FTAG; set(rhs); return *this; } \
TYPENAME& operator=(const TYPENAME& rhs) { TYPES_H_FTAG; set(rhs); return *this; } \
};
// Disable PVS-Studio warning V730: Not all members of a class are initialized inside the constructor (local_buf is not initialized and that is fine)
// -V:STR_DEFINETYPE:730
// Helper to define StringXXXf constructors
#define STR_DEFINETYPE_F(TYPENAME, TYPENAME_F) \
class TYPENAME_F : public TYPENAME \
{ \
public: \
TYPENAME_F(const char* fmt, ...) : TYPENAME() { TYPES_H_FTAG; va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \
};
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-private-field" // warning : private field 'local_buf' is not used
#endif
// Declaring types for common sizes here
STR_DEFINETYPE(String16, 16)
STR_DEFINETYPE(String30, 30)
STR_DEFINETYPE(String64, 64)
STR_DEFINETYPE(String128, 128)
STR_DEFINETYPE(String256, 256)
STR_DEFINETYPE(String512, 512)
// Declaring helper constructors to pass in format strings in one statement
STR_DEFINETYPE_F(String16, String16f)
STR_DEFINETYPE_F(String30, String30f)
STR_DEFINETYPE_F(String64, String64f)
STR_DEFINETYPE_F(String128, String128f)
STR_DEFINETYPE_F(String256, String256f)
STR_DEFINETYPE_F(String512, String512f)
#if STR_DEFINE_STR32
STR_DEFINETYPE(String32, 32)
STR_DEFINETYPE_F(String32, String32f)
#endif
// by default, if you use a string type with a local buffer, and append to it with something bigger than the local buffer,
// you end up realloc'ing space to fit, and this reallocation is stingy - only exactly as much as needed.
// if you are creating a string which may be repeatedly concatenated with other strings/appended to, it's better
// to do a smaller number of growing allocations along the lines of what dynamic arrays do.
// this type is the same as other string types, but if it has to reserve additional space for itself, it will do so
// eagerly, at a rate of 1.5x
#define STRINGBUFFER_DEFINETYPE(TYPENAME, LOCALBUFSIZE) \
class TYPENAME : public String { \
char local_buf[LOCALBUFSIZE]; \
public: \
TYPENAME(const char* fmt, ...) : String(LOCALBUFSIZE) { TYPES_H_FTAG; va_list args; va_start(args, fmt); setfv(fmt, args); va_end(args); } \
TYPENAME() : String(LOCALBUFSIZE) { TYPES_H_FTAG; } \
TYPENAME(const String& rhs) : String(LOCALBUFSIZE) { TYPES_H_FTAG; set(rhs); } \
TYPENAME(const char* rhs) : String(LOCALBUFSIZE) { TYPES_H_FTAG; set(rhs); } \
TYPENAME(const TYPENAME& rhs) : String(LOCALBUFSIZE) { TYPES_H_FTAG; set(rhs); } \
TYPENAME& operator=(const char* rhs) { TYPES_H_FTAG; set(rhs); return *this; } \
TYPENAME& operator=(const String& rhs) { TYPES_H_FTAG; set(rhs); return *this; } \
TYPENAME& operator=(const TYPENAME& rhs) { TYPES_H_FTAG; set(rhs); return *this; } \
void reserve(int new_capacity) { \
TYPES_H_FTAG; \
if (new_capacity <= Capacity) \
return; \
char* new_data; \
if (new_capacity < LocalBufSize) { \
new_data = (char*)this + sizeof(String); \
new_capacity = LocalBufSize; \
} else { \
new_data = (char*)STR_MEMALLOC((size_t)new_capacity * sizeof(char)); \
} \
strncpy(new_data, Data, (size_t)new_capacity - 1); \
new_data[new_capacity - 1] = 0; \
if (Owned && !is_using_local_buf()) \
STR_MEMFREE(Data); \
Data = new_data; \
Capacity = new_capacity; \
Owned = 1; \
} \
void reserve_discard(int new_capacity) { \
TYPES_H_FTAG; \
if (new_capacity <= Capacity) \
return; \
if (Owned && !is_using_local_buf()) \
STR_MEMFREE(Data); \
if (new_capacity < LocalBufSize) { \
Data = (char*)this + sizeof(String); \
Capacity = LocalBufSize; \
} else { \
while (Capacity < new_capacity) { \
Capacity *= 1.5; \
Data = (char*) STR_MEMALLOC((size_t) Capacity * sizeof(char)); \
} \
} \
Owned = 1; \
} \
};
STRINGBUFFER_DEFINETYPE(StringBuffer512, 512)
#ifdef __clang__
#pragma clang diagnostic pop
#endif
// On some platform vsnprintf() takes va_list by reference and modifies it.
// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
#ifndef va_copy
#define va_copy(dest, src) (dest = src)
#endif
//-------------------------------------------------------------------------
// https://www.fileformat.info/info/unicode/category/Zs/list.htm
/* @TODO
bool isUnicodeSpaceSeparator(char c) {
switch (c) {
case 0x20:
case 0xA0:
case 0x1680:
case 0x2000:
case 0x2001:
case 0x2002:
case 0x2003:
case 0x2004:
case 0x2005:
case 0x2006:
case 0x2007:
case 0x2008:
case 0x2009:
case 0x200A:
case 0x202F:
case 0x205F:
case 0x3000:
return true;
default:
return false;
}
}
*/
//struct StringBuffer {
// u32 length;
// u32 capacity;
// char* data;
//
// StringBuffer(u32 initialSize = 2048);
// StringBuffer(const char* string);
// ~StringBuffer();
//
// void checkIfShouldGrow();
// bool isEmpty() const;
// bool isFull() const;
// char pop();
// u32 append(char e);
//};
////================================================================================
//StringBuffer::StringBuffer(u32 initialSize) {
// TYPES_H_FTAG;
// this->length = 0;
// this->capacity = initialSize;
// this->data = (char*) pMalloc(sizeof(char) * this->capacity);
//}
//
//StringBuffer::StringBuffer(const char* string) {
// this->length = String::len(string);
// this->capacity = this->length;
// this->data = cpy(string, this->length);
//}
//
//StringBuffer::~StringBuffer() {
// pFree(this->data);
//}
//
//void StringBuffer::checkIfShouldGrow() {
// TYPES_H_FTAG;
// if (this->isFull()) {
// // optimal number as you approach infinite elements approaches PHI, but 1.5 sometimes works better for finite sizes
// // more testing is probably needed
// this->capacity = (u32) (this->capacity * 1.5);
// this->data = (char*) pRealloc(this->data, sizeof(char) * this->capacity);
// }
//}
//
//bool StringBuffer::isEmpty() const {
// TYPES_H_FTAG;
// return this->length == 0;
//}
//
//bool StringBuffer::isFull() const {
// TYPES_H_FTAG;
// return this->length == this->capacity;
//}
//
//char StringBuffer::pop() {
// TYPES_H_FTAG;
// if (this->isEmpty()) {
// die("empty");
// }
//
// return this->data[--this->length];
//}
//
//u32 StringBuffer::append(char e) {
// TYPES_H_FTAG;
// this->checkIfShouldGrow();
//
// this->data[this->length++] = e;
//
// return this->length - 1;
//}
#endif

720
table.hpp

@ -0,0 +1,720 @@
#ifndef TABLE_H
#define TABLE_H
#include <new> // new
#include <functional> // std::function for traversal
#include <type_traits> // std::enable_if
#include "alloc.h"
#include "string.h"
#include "types.h"
// what follows is a collection of hash functions taken from: https://www.partow.net/programming/hashfunctions/#:~:text=The%20hash%20functions%20in%20this,containers%20such%20as%20hash%2Dtables.
//
// Available Hash Functions
// The General Hash Functions Library has the following mix of additive and rotative general purpose string hashing algorithms. The following algorithms vary in usefulness and functionality and are mainly intended as an example for learning how hash functions operate and what they basically look like in code form.
//
// 00 - RS Hash Function
// A simple hash function from Robert Sedgwicks Algorithms in C book. I've added some simple optimizations to the algorithm in order to speed up its hashing process.
#if 1
static unsigned int RSHash(const char* str, unsigned int length)
{
unsigned int b = 378551;
unsigned int a = 63689;
unsigned int hash = 0;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = hash * a + (*str);
a = a * b;
}
return hash;
}
#endif
// 01 - JS Hash Function
// A bitwise hash function written by Justin Sobel
#if 1
static unsigned int JSHash(const char* str, unsigned int length)
{
unsigned int hash = 1315423911;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash ^= ((hash << 5) + (*str) + (hash >> 2));
}
return hash;
}
#endif
// 02 - PJW Hash Function
// This hash algorithm is based on work by Peter J. Weinberger of Renaissance Technologies. The book Compilers (Principles, Techniques and Tools) by Aho, Sethi and Ulman, recommends the use of hash functions that employ the hashing methodology found in this particular algorithm.
#if 1
static unsigned int PJWHash(const char* str, unsigned int length)
{
const unsigned int BitsInUnsignedInt = (unsigned int)(sizeof(unsigned int) * 8);
const unsigned int ThreeQuarters = (unsigned int)((BitsInUnsignedInt * 3) / 4);
const unsigned int OneEighth = (unsigned int)(BitsInUnsignedInt / 8);
const unsigned int HighBits =
(unsigned int)(0xFFFFFFFF) << (BitsInUnsignedInt - OneEighth);
unsigned int hash = 0;
unsigned int test = 0;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = (hash << OneEighth) + (*str);
if ((test = hash & HighBits) != 0)
{
hash = (( hash ^ (test >> ThreeQuarters)) & (~HighBits));
}
}
return hash;
}
#endif
// 03 - ELF Hash Function
// Similar to the PJW Hash function, but tweaked for 32-bit processors. It is a widley used hash function on UNIX based systems.
#if 1
static unsigned int ELFHash(const char* str, unsigned int length)
{
unsigned int hash = 0;
unsigned int x = 0;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = (hash << 4) + (*str);
if ((x = hash & 0xF0000000L) != 0)
{
hash ^= (x >> 24);
}
hash &= ~x;
}
return hash;
}
#endif
// 04 - BKDR Hash Function
// This hash function comes from Brian Kernighan and Dennis Ritchie's book "The C Programming Language". It is a simple hash function using a strange set of possible seeds which all constitute a pattern of 31....31...31 etc, it seems to be very similar to the DJB hash function.
//
// @NOTE (nick) - this is basically the one I was using before I found this resource with all these other hash functions. (I took it from K&R)
// except, the seed in the version I used was '31', not '131'.
#if 1
static unsigned int BKDRHash(const char* str, unsigned int length)
{
unsigned int seed = 131; /* 31 131 1313 13131 131313 etc.. */
unsigned int hash = 0;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = (hash * seed) + (*str);
}
return hash;
}
#endif
// 05 - SDBM Hash Function
// This is the algorithm of choice which is used in the open source SDBM project. The hash function seems to have a good over-all distribution for many different data sets. It seems to work well in situations where there is a high variance in the MSBs of the elements in a data set.
#if 1
static unsigned int SDBMHash(const char* str, unsigned int length)
{
unsigned int hash = 0;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = (*str) + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
#endif
// 06 - DJB Hash Function
// An algorithm produced by Professor Daniel J. Bernstein and shown first to the world on the usenet newsgroup comp.lang.c. It is one of the most efficient hash functions ever published.
#if 1
static unsigned int DJBHash(const char* str, unsigned int length)
{
unsigned int hash = 5381;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = ((hash << 5) + hash) + (*str);
}
return hash;
}
#endif
// 07 - DEK Hash Function
// An algorithm proposed by Donald E. Knuth in The Art Of Computer Programming Volume 3, under the topic of sorting and search chapter 6.4.
#if 1
static unsigned int DEKHash(const char* str, unsigned int length)
{
unsigned int hash = length;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash = ((hash << 5) ^ (hash >> 27)) ^ (*str);
}
return hash;
}
#endif
// 08 - AP Hash Functionhing The Project Gutenberg Etext of Webster's Unabridged Dictionary, the longest encountered chain length was 7, the average chain length was 2, the number of empty buckets was 4579.
// An algorithm produced by me Arash Partow. I took ideas from all of the above hash functions making a hybrid rotative and additive hash function algorithm. There isn't any real mathematical analysis explaining why one should use this hash function instead of the others described above other than the fact that I tired to resemble the design as close as possible to a simple LFSR. An empirical result which demonstrated the distributive abilities of the hash algorithm was obtained using a hash-table with 100003 buckets, hashing The Project Gutenberg Etext of Webster's Unabridged Dictionary, the longest encountered chain length was 7, the average chain length was 2, the number of empty buckets was 4579.
// @NOTE(nick) - given the test case above of the whole english dictionary, I think this one makes sense for a general purpose, unknown-length key hash table.
#if 1
static unsigned int APHash(const char* str, unsigned int length)
{
unsigned int hash = 0xAAAAAAAA;
unsigned int i = 0;
for (i = 0; i < length; ++str, ++i)
{
hash ^= ((i & 1) == 0) ? ( (hash << 7) ^ (*str) * (hash >> 3)) :
(~((hash << 11) + ((*str) ^ (hash >> 5))));
}
return hash;
}
#endif
static inline u32 fastModuloReductionDanielLemire(u32 v, u32 c) {
return (((u64)v) * ((u64)c)) >> 32;
}
static inline u32 hash(const char* key, u32 keyLength, u32 capacity) {
TYPES_H_FTAG;
u32 value = APHash(key, keyLength);
return fastModuloReductionDanielLemire(value, capacity);
}
template <typename V>
struct TableEntry {
TableEntry<V>* next;
const char* key;
u32 keyLength;
V value;
};
template <typename T>
static void serialize(String* str, TableEntry<T>* entry) {
serialize(str, entry->key);
serialize(str, entry->keyLength);
serialize(str, entry->value);
}
template <typename V>
struct Table {
u32 lanes;
u32 length;
TableEntry<V>** entries;
Table<V>(u32 _lanes = 16) {
TYPES_H_FTAG;
this->lanes = _lanes;
this->length = 0;
this->entries = (TableEntry<V>**) pCalloc(sizeof(TableEntry<V>*), this->lanes);
}
void* operator new(size_t size) {
TYPES_H_FTAG;
return (Table<V>*) pMalloc(sizeof(Table<V>));
}
V insert(const char* key, u32 keyLength, V value) {
TYPES_H_FTAG;
TableEntry<V>* entry = this->lookup(key, keyLength);
if (!entry) { // no entry with that key exists
entry = (TableEntry<V>*) pMalloc(sizeof (TableEntry<V>));
entry->key = String::cpy(key, keyLength);
entry->keyLength = keyLength;
entry->value = value;
u32 hashValue = hash(key, keyLength, lanes);
entry->next = entries[hashValue];
entries[hashValue] = entry;
this->length++;
return (V) 0;
} else { // entry already exists, replace its value
// pFree(entry->value); // @NOTE how to cleanup if overwriting an owned pointer?
V oldValue = entry->value;
entry->value = value;
return oldValue;
}
}
TableEntry<V>* lookup(const char* key, u32 keyLength) {
TYPES_H_FTAG;
TableEntry<V>* entry = this->entries[hash(key, keyLength, lanes)];
for (; entry != null; entry = entry->next) {
if (String::memeq((unsigned char*)key, keyLength, (unsigned char*)entry->key, entry->keyLength)) {
return entry;
}
}
return null;
}
V lookupWithDefault(const char* key, u32 keyLength, V defaultValue) {
TYPES_H_FTAG;
auto entry = this->lookup(key, keyLength);
if (entry == null) return defaultValue;
return entry->value;
}
// do not set |freeValues| to true unless the template parameter 'T' is a pointer,
// and the table is responsible for freeing the memory.
void clear(bool freeValues = false) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->lanes; i++) {
TableEntry<V>** lane = &this->entries[i];
TableEntry<V>* entry = *lane;
while (entry != null) {
auto next = entry->next;
pFree(entry->key);
if (freeValues) {
// @HACK - it's only relevant to free the value if it's an owned pointer
// (the table is effectively 'responsible' for that memory)
// but you may have 'V' be a non-pointer value entirely, causing this cast to
// be nonsensical/a bug in other cases.
//
// make sure you know what you're doing when you set |freeValues| to |true|.
//
// there's probably a 'better' way to do this using C++ template voodoo,
// but I have no interest in digging myself a deeper grave there.
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
#endif
pFree((void*) entry->value);
#ifdef __clang__
#pragma clang diagnostic pop
#endif
}
pFree(entry);
entry = next;
}
*lane = null;
}
this->length = 0;
}
void traverse(const std::function <void (TableEntry<V>*)>& entryCallback) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->lanes; i++) {
TableEntry<V>* entry = this->entries[i];
while (entry != null) {
entryCallback(entry);
entry = entry->next;
}
}
}
};
template <typename T>
static void serialize(String* str, Table<T> table) {
TYPES_H_FTAG;
serialize(str, table.lanes);
serialize(str, table.length);
for (u32 i = 0; i < table.lanes; i++) {
TableEntry<T>* entry = table.entries[i];
while (entry) {
serialize(str, entry);
entry = entry->next;
}
}
}
template <typename T>
static void serialize(String* str, Table<T>* table) {
TYPES_H_FTAG;
SERIALIZE_HANDLE_NULL(str, table);
serialize(str, table->lanes);
serialize(str, table->length);
for (u32 i = 0; i < table->lanes; i++) {
TableEntry<T>* entry = table->entries[i];
while (entry) {
serialize(str, entry);
entry = entry->next;
}
}
}
template <typename T>
static void deserialize(char** buffer, Table<T>* table) {
TYPES_H_FTAG;
deserialize(buffer, &table->lanes);
u32 length;
deserialize(buffer, &length);
for (u32 i = 0; i < length; i++) {
TableEntry<T> entry;
deserialize(buffer, &entry.key);
deserialize(buffer, &entry.keyLength);
deserialize(buffer, &entry.value);
table->insert(entry.key, entry.keyLength, entry.value);
pFree(entry.key);
}
table->length = length;
}
template <typename T>
static void deserialize(char** buffer, Table<T>** table) {
TYPES_H_FTAG;
DESERIALIZE_HANDLE_NULL(buffer, table);
u32 lanes;
deserialize(buffer, &lanes);
Table<T>* _table = new Table<T>(lanes);
u32 length;
deserialize(buffer, &length);
for (u32 i = 0; i < length; i++) {
TableEntry<T> entry;
deserialize(buffer, &entry.key);
deserialize(buffer, &entry.keyLength);
deserialize(buffer, &entry.value);
_table->insert(entry.key, entry.keyLength, entry.value);
pFree(entry.key);
}
_table->length = length;
*table = _table;
}
//================================================================================
// Fixed-key size table.
//
// Sometimes, you want a hash table and you know for a fact how big the keys will
// be at maximum.
//
// This commonly happens when the keys actually aren't strings, but are integers,
// or other packed values.
//
// You can use this instead in that case, and avoid keeping certain memory and
// some work the more generic table has to deal with.
//
// You'll enjoy some speedup from MMX/SSE/AVX if they are supported and you use
// a key size of 16, 32, or 64.
//
//================================================================================
//#include <mmintrin.h>
template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE == 64>::type* = nullptr>
static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
TYPES_H_FTAG;
// AVX512:
//__mmask32 result = _mm512_cmpeq_epi16_mask (*((__m512i*)m1), *((__m512i*)m2));
//sse4.2:
//int result = 0;
//for (u32 i = 0; i < 4; i++) {
// __m128i s1 = *((__m128i*)(m1+(i*16)));
// __m128i s2 = *((__m128i*)(m2+(i*16)));
// result = _mm_cmpistro(s1, s2, _SIDD_UBYTE_OPS);
//}
// MMX: (this one is barely nanoseconds (~1-10ns) faster than String::memeq)
//for (u32 i = 0; i < 4; i++) {
// u32 ii = i*16;
// __m64 s1 = *((__m64*)(m1+ii));
// __m64 s2 = *((__m64*)(m2+ii));
// __m64 result = _mm_cmpeq_pi32(s1, s2);
// if (((u64)result) != ~0ULL) {
// return false;
// }
//}
//return true;
return String::memeq(m1, m2, KEY_SIZE);
}
template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE == 32>::type* = nullptr>
static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
TYPES_H_FTAG;
//sse4.2:
//int result = 0;
//for (u32 i = 0; i < 4; i++) {
// __m128i s1 = *((__m128i*)(m1+(i*16)));
// __m128i s2 = *((__m128i*)(m2+(i*16)));
// result = _mm_cmpistro(s1, s2, _SIDD_UBYTE_OPS);
//}
// MMX: (this one is barely nanoseconds (~1-10ns) faster than String::memeq)
//for (u32 i = 0; i < 2; i++) {
// u32 ii = i*16;
// __m64 s1 = *((__m64*)(m1+ii));
// __m64 s2 = *((__m64*)(m2+ii));
// __m64 result = _mm_cmpeq_pi32(s1, s2);
// if (((u64)result) != ~0ULL) {
// return false;
// }
//}
//return true;
return String::memeq(m1, m2, KEY_SIZE);
}
template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE == 16>::type* = nullptr>
static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
TYPES_H_FTAG;
// MMX: (this one is barely nanoseconds (~1-10ns) faster than String::memeq)
//__m64 result = _mm_cmpeq_pi32(*((__m64*)m1), *((__m64*)m2));
//return ((u64)result) == ~0ULL;
return String::memeq(m1, m2, KEY_SIZE);
}
template <size_t KEY_SIZE, typename std::enable_if<KEY_SIZE != 64 && KEY_SIZE != 32 && KEY_SIZE != 16>::type* = nullptr>
static inline bool fixedKeySizeMemEq(u8* m1, u8* m2) {
TYPES_H_FTAG;
return String::memeq(m1, m2, KEY_SIZE);
}
template <size_t KEY_SIZE, typename V>
struct FixedKeySizeTableEntry {
FixedKeySizeTableEntry<KEY_SIZE, V>* next;
const char key[KEY_SIZE];
V value;
};
template <size_t KEY_SIZE, typename V>
struct FixedKeySizeTable {
u32 lanes;
u32 length;
FixedKeySizeTableEntry<KEY_SIZE, V>** entries;
FixedKeySizeTable<KEY_SIZE, V>(u32 _lanes = 16) {
TYPES_H_FTAG;
this->lanes = _lanes;
this->length = 0;
this->entries = (FixedKeySizeTableEntry<KEY_SIZE, V>**) pCalloc(sizeof(FixedKeySizeTableEntry<KEY_SIZE, V>*), this->lanes);
}
void* operator new(size_t size) {
TYPES_H_FTAG;
return (FixedKeySizeTable<KEY_SIZE, V>*) pMalloc(sizeof(FixedKeySizeTable<KEY_SIZE, V>));
}
V insert(const char* key, V value) {
TYPES_H_FTAG;
FixedKeySizeTableEntry<KEY_SIZE, V>* entry = this->lookup(key);
if (!entry) { // no entry with that key exists
entry = (FixedKeySizeTableEntry<KEY_SIZE, V>*) pCalloc(sizeof(FixedKeySizeTableEntry<KEY_SIZE, V>), 1);
String::write((char*)entry->key, key, KEY_SIZE);
entry->value = value;
u32 hashValue = hash(key, KEY_SIZE, lanes);
entry->next = entries[hashValue];
entries[hashValue] = entry;
this->length++;
return (V) 0;
} else { // entry already exists, replace its value
// pFree(entry->value); // @NOTE how to cleanup if overwriting an owned pointer?
V oldValue = entry->value;
entry->value = value;
return oldValue;
}
}
FixedKeySizeTableEntry<KEY_SIZE, V>* lookup(const char* key) {
TYPES_H_FTAG;
FixedKeySizeTableEntry<KEY_SIZE, V>* entry = this->entries[hash(key, KEY_SIZE, lanes)];
for (; entry != null; entry = entry->next) {
if (fixedKeySizeMemEq<KEY_SIZE>((unsigned char*)key, (unsigned char*)entry->key)) {
return entry;
}
}
return null;
}
V lookupWithDefault(const char* key, V defaultValue) {
TYPES_H_FTAG;
auto entry = this->lookup(key);
if (entry == null) return defaultValue;
return entry->value;
}
// do not set |freeValues| to true unless the template parameter 'T' is a pointer,
// and the table is responsible for freeing the memory.
void clear(bool freeValues = false) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->lanes; i++) {
FixedKeySizeTableEntry<KEY_SIZE, V>** lane = &this->entries[i];
FixedKeySizeTableEntry<KEY_SIZE, V>* entry = *lane;
while (entry != null) {
auto next = entry->next;
if (freeValues) {
// @HACK - it's only relevant to free the value if it's an owned pointer
// (the table is effectively 'responsible' for that memory)
// but you may have 'V' be a non-pointer value entirely, causing this cast to
// be nonsensical/a bug in other cases.
//
// make sure you know what you're doing when you set |freeValues| to |true|.
//
// there's probably a 'better' way to do this using C++ template voodoo,
// but I have no interest in digging myself a deeper grave there.
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
#endif
pFree((void*) entry->value);
#ifdef __clang__
#pragma clang diagnostic pop
#endif
}
pFree(entry);
entry = next;
}
*lane = null;
}
this->length = 0;
}
void traverse(const std::function <void (FixedKeySizeTableEntry<KEY_SIZE, V>*)>& entryCallback) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->lanes; i++) {
FixedKeySizeTableEntry<KEY_SIZE, V>* entry = this->entries[i];
while (entry != null) {
entryCallback(entry);
entry = entry->next;
}
}
}
};
//================================================================================
// a better explaination of cache tables than I could possibly do in a comment:
// https://fgiesen.wordpress.com/2019/02/11/cache-tables/
struct CacheTableEntry {
char* key;
u32 keyLength;
void* value;
};
struct CacheTable {
u32 n, p;
CacheTableEntry* entries; // n and p are the dimensions of the array. n is first.
CacheTable(u32 _n = 8, u32 _p = 8) {
TYPES_H_FTAG;
this->n = _n;
this->p = _p;
this->entries = (CacheTableEntry*) pCalloc(this->n*this->p, sizeof(CacheTableEntry));
}
void* insert(const char* key, u32 keyLength, void* value) {
TYPES_H_FTAG;
CacheTableEntry* row = this->entries + hash(key, keyLength, this->n) * this->n;
// We're going to insert in 'row'. We need some policy to decide which column to evict.
// The (stupid) choice for now, is to just start at column 0, and increment every time we insert,
// regardless of which row we chose.
static u32 seed = 0;
// increment, then modulo the number of columns.
seed = ((u64) (seed+1) * (u64) this->p) >> 32;
CacheTableEntry* entry = row + seed;
if (entry->key != null) {
// the entry was already populated. we have to free the memory for the key
// (because we always copy keys on insertion)
// as well as return the address of the old value we overwrite, so the caller can free it
// if necessary.
pFree(entry->key);
entry->key = String::cpy(key, keyLength);
entry->keyLength = keyLength;
void* oldValue = entry->value;
entry->value = value;
return oldValue;
} else {
entry->key = String::cpy(key, keyLength);
entry->keyLength = keyLength;
entry->value = value;
return null;
}
}
CacheTableEntry* lookup(const char* key, u32 keyLength) {
TYPES_H_FTAG;
CacheTableEntry* row = this->entries + hash(key, keyLength, this->n) * this->n;
for (u32 i = 0; i < this->p; i++) {
CacheTableEntry* entry = row + i;
if (String::memeq((unsigned char*)key, keyLength, (unsigned char*)entry->key, entry->keyLength)) {
return entry;
}
}
return null;
}
void clear(bool freeValues = false) {
TYPES_H_FTAG;
for (u32 i = 0; i < this->n; i++) {
CacheTableEntry* row = this->entries + i * this->n;
for (u32 j = 0; j < this->p; j++) {
CacheTableEntry* entry = row + j;
if (entry->key != null) {
pFree(entry->key);
}
if (freeValues && entry->value != null) {
pFree(entry->value);
}
}
}
}
};
#endif

111
types.h

@ -0,0 +1,111 @@
#ifndef TYPES_H
#define TYPES_H
#include <stddef.h> // size_t
#define null 0
// long term, it would be nice to not have to '#include' tracy here,
// a client using the library should include it and use a define to instruct
// the library what to put at the beginning of function calls for profiling needs,
// but i've had trouble implementing that.
#ifndef TYPES_H_FTAG
#include <Tracy.hpp>
#define TYPES_H_FTAG ZoneScoped
#endif
// bool is included by default for C++11
#ifndef __cplusplus
typedef _Bool bool;
#define true 1
#define false 0
#endif
// The restrict declspec is used on functions that return unaliased pointers. This keyword is used for the C-Runtime Library implementation of malloc since it will never return a pointer value that is already in use in the current program (unless you are doing something illegal, such as using memory after it has been freed).
// https://learn.microsoft.com/en-us/cpp/cpp/restrict?view=msvc-170
// @NOTE(gingerbill posted this):
//#if !defined(restrict)
// #if defined(_MSC_VER)
// #define shard_restrict __restrict
// #elif defined(__STDC_VERSION__)
// #define shard_restrict restrict
// #else
// #define shard_restrict
// #endif
//#endif
// The noalias declspec is also applied only to functions, and indicates that the function is a semi-pure function. A semi-pure function is one that references or modifies only locals, arguments, and first-level indirections of arguments. This declspec is a promise to the compiler, and if the function references globals or second-level indirections of pointer arguments then the compiler may generate code that breaks the application.
// https://learn.microsoft.com/en-us/cpp/cpp/noalias?view=msvc-170
// char
// unsigned char
#include <stdint.h>
typedef uint64_t u64;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef int64_t s64;
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
//typedef size_t size_t;
// we use the standard C names for IEEE-754 binary64 and binary32 (double, float). we don't use 'extended'.
//typedef float float;
//typedef double double;
//typedef long double extended;
// if we're using the glm vector/matrix types, or other types, define them here
#define _USING_GLM_TYPES__
#ifdef _USING_GLM_TYPES__
// force high precision for everything
#define GLM_PRECISION_HIGHP_FLOAT
#define GLM_PRECISION_HIGHP_DOUBLE
#define GLM_PRECISION_HIGHP_INT
#define GLM_PRECISION_HIGHP_UINT
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#endif
typedef enum TypeEnum {
TypeEnum_bool,
TypeEnum_char,
TypeEnum_uchar,
TypeEnum_u8,
TypeEnum_u16,
TypeEnum_u32,
TypeEnum_u64,
TypeEnum_s8,
TypeEnum_s16,
TypeEnum_s32,
TypeEnum_s64,
#ifndef _WIN32
TypeEnum_size_t,
#endif
TypeEnum_float,
TypeEnum_double,
#ifdef _USING_GLM_TYPES__
TypeEnum_vec2,
TypeEnum_vec3,
TypeEnum_vec4,
TypeEnum_mat2,
TypeEnum_mat3,
TypeEnum_mat4,
#endif
} TypeEnum;
#endif

10
util.h

@ -0,0 +1,10 @@
#ifndef UTIL_H
#define UTIL_H
#define STATIC_ARRAY_LENGTH(a) (sizeof(a)/sizeof(a[0]))
#endif
Loading…
Cancel
Save