Add defer system; Rework exceptions; util cleanup;

Defer can be used to clean up resources when an error occurs.
This commit is contained in:
bjorn 2024-03-27 12:48:35 -07:00
parent 67e228125b
commit cdecc1bae7
14 changed files with 200 additions and 114 deletions

View File

@ -1059,7 +1059,7 @@ static int l_lovrGraphicsNewShader(lua_State* L) {
lovrGraphicsCompileShader(source, compiled, info.stageCount, luax_readfile);
arr_t(ShaderFlag) flags;
arr_init(&flags, realloc);
arr_init(&flags);
if (lua_istable(L, index)) {
lua_getfield(L, index, "flags");

View File

@ -9,7 +9,7 @@ static int l_lovrShaderClone(lua_State* L) {
lua_pushnil(L);
arr_t(ShaderFlag) flags;
arr_init(&flags, realloc);
arr_init(&flags);
while (lua_next(L, 2) != 0) {
ShaderFlag flag = { 0 };

View File

@ -8,26 +8,34 @@
#include <stdlib.h>
#include <string.h>
static void threadRun(void* L) {
int top = lua_gettop(L);
int status = lua_pcall(L, top - 2, 0, 1);
lua_pushinteger(L, status);
}
static char* threadRunner(Thread* thread, Blob* body, Variant* arguments, uint32_t argumentCount) {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
luax_preload(L);
lovrSetErrorCallback((errorFn*) luax_vthrow, L);
lua_pushcfunction(L, luax_getstack);
int errhandler = lua_gettop(L);
if (!luaL_loadbuffer(L, body->data, body->size, "thread")) {
for (uint32_t i = 0; i < argumentCount; i++) {
luax_pushvariant(L, &arguments[i]);
}
if (!lua_pcall(L, argumentCount, 0, errhandler)) {
lovrTry(threadRun, L, luax_vthrow, L);
if (lua_tointeger(L, -1) == 0) {
lua_close(L);
return NULL;
} else {
lua_pop(L, 1);
}
}
// Error handling
size_t length;
const char* message = lua_tolstring(L, -1, &length);
@ -92,15 +100,18 @@ int luaopen_lovr_thread(lua_State* L) {
int32_t workers = -1;
luax_pushconf(L);
lua_getfield(L, -1, "thread");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "workers");
if (lua_type(L, -1) == LUA_TNUMBER) {
workers = lua_tointeger(L, -1);
lua_getfield(L, -1, "thread");
if (lua_istable(L, -1)) {
lua_getfield(L, -1, "workers");
if (lua_type(L, -1) == LUA_TNUMBER) {
workers = lua_tointeger(L, -1);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lua_pop(L, 2);
lua_pop(L, 1);
lovrThreadModuleInit(workers);
luax_atexit(L, lovrThreadModuleDestroy);

View File

@ -10,6 +10,12 @@
#include <string.h>
#include <stdlib.h>
static void run(void* T) {
while (luax_resume(T, 0) == LUA_YIELD) {
os_sleep(0.);
}
}
int main(int argc, char** argv) {
os_init();
@ -37,12 +43,8 @@ int main(int argc, char** argv) {
}
lua_State* T = lua_tothread(L, -1);
lovrSetErrorCallback(luax_vthrow, T);
lovrSetLogCallback(luax_vlog, T);
while (luax_resume(T, 0) == LUA_YIELD) {
os_sleep(0.);
}
lovrTry(run, T, luax_vthrow, T);
if (lua_type(T, 1) == LUA_TSTRING && !strcmp(lua_tostring(T, 1), "restart")) {
luax_checkvariant(T, 2, &cookie);

View File

@ -123,16 +123,16 @@ ModelData* lovrModelDataInitObj(ModelData* model, Blob* source, ModelDataIO* io)
arr_t(float) normals;
arr_t(float) uvs;
arr_init(&groups, arr_alloc);
arr_init(&images, arr_alloc);
arr_init(&materials, arr_alloc);
arr_init(&groups);
arr_init(&images);
arr_init(&materials);
map_init(&materialMap, 0);
arr_init(&vertexBlob, arr_alloc);
arr_init(&indexBlob, arr_alloc);
arr_init(&vertexBlob);
arr_init(&indexBlob);
map_init(&vertexMap, 0);
arr_init(&positions, arr_alloc);
arr_init(&normals, arr_alloc);
arr_init(&uvs, arr_alloc);
arr_init(&positions);
arr_init(&normals);
arr_init(&uvs);
arr_push(&groups, ((objGroup) { .material = -1 }));

View File

@ -22,7 +22,7 @@ void lovrVariantDestroy(Variant* variant) {
bool lovrEventInit(void) {
if (atomic_fetch_add(&state.ref, 1)) return false;
arr_init(&state.events, arr_alloc);
arr_init(&state.events);
return true;
}

View File

@ -568,7 +568,7 @@ static bool zip_init(Archive* archive, const char* filename, const char* root) {
// Parse the number of file entries and reserve memory
uint16_t nodeCount = readu16(p + 10);
arr_init(&archive->nodes, realloc);
arr_init(&archive->nodes);
arr_reserve(&archive->nodes, nodeCount);
map_init(&archive->lookup, nodeCount);

View File

@ -689,9 +689,9 @@ bool lovrGraphicsInit(GraphicsConfig* config) {
map_init(&state.passLookup, 4);
map_init(&state.pipelineLookup, 64);
arr_init(&state.layouts, realloc);
arr_init(&state.materialBlocks, realloc);
arr_init(&state.scratchTextures, realloc);
arr_init(&state.layouts);
arr_init(&state.materialBlocks);
arr_init(&state.scratchTextures);
gpu_slot builtinSlots[] = {
{ 0, GPU_SLOT_UNIFORM_BUFFER, GPU_STAGE_GRAPHICS }, // Globals
@ -3467,7 +3467,7 @@ Font* lovrFontCreate(const FontInfo* info) {
font->ref = 1;
font->info = *info;
lovrRetain(info->rasterizer);
arr_init(&font->glyphs, realloc);
arr_init(&font->glyphs);
map_init(&font->glyphLookup, 36);
map_init(&font->kerning, 36);

View File

@ -131,7 +131,7 @@ static void evaluate(float* restrict P, size_t n, float t, vec4 p) {
Curve* lovrCurveCreate(void) {
Curve* curve = lovrCalloc(sizeof(Curve));
curve->ref = 1;
arr_init(&curve->points, arr_alloc);
arr_init(&curve->points);
arr_reserve(&curve->points, 16);
return curve;
}

View File

@ -398,8 +398,8 @@ Collider* lovrColliderCreate(World* world, float x, float y, float z) {
lovrColliderSetAngularDamping(collider, world->defaultAngularDamping, 0.f);
lovrColliderSetSleepingAllowed(collider, world->defaultIsSleepingAllowed);
arr_init(&collider->shapes, arr_alloc);
arr_init(&collider->joints, arr_alloc);
arr_init(&collider->shapes);
arr_init(&collider->joints);
// Adjust the world's collider list
if (!collider->world->head) {

View File

@ -164,7 +164,7 @@ World* lovrWorldCreate(float xg, float yg, float zg, bool allowSleep, const char
world->space = dHashSpaceCreate(0);
dHashSpaceSetLevels(world->space, -4, 8);
world->contactGroup = dJointGroupCreate(0);
arr_init(&world->overlaps, arr_alloc);
arr_init(&world->overlaps);
lovrWorldSetGravity(world, xg, yg, zg);
lovrWorldSetSleepingAllowed(world, allowSleep);
for (uint32_t i = 0; i < tagCount; i++) {
@ -448,8 +448,8 @@ Collider* lovrColliderCreate(World* world, float x, float y, float z) {
collider->restitution = 0;
collider->tag = NO_TAG;
dBodySetData(collider->body, collider);
arr_init(&collider->shapes, arr_alloc);
arr_init(&collider->joints, arr_alloc);
arr_init(&collider->shapes);
arr_init(&collider->joints);
lovrColliderSetPosition(collider, x, y, z);

View File

@ -171,7 +171,7 @@ const char* lovrThreadGetError(Thread* thread) {
Channel* lovrChannelCreate(uint64_t hash) {
Channel* channel = lovrCalloc(sizeof(Channel));
channel->ref = 1;
arr_init(&channel->messages, arr_alloc);
arr_init(&channel->messages);
mtx_init(&channel->lock, mtx_plain);
cnd_init(&channel->cond);
channel->hash = hash;

View File

@ -1,25 +1,28 @@
#include "util.h"
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <setjmp.h>
#include <stdio.h>
// Allocation
void* lovrMalloc(size_t size) {
void* data = malloc(size);
if (!data) fprintf(stderr, "Out of memory"), abort();
lovrAssert(data, "Out of memory");
return data;
}
void* lovrCalloc(size_t size) {
void* data = calloc(1, size);
if (!data) fprintf(stderr, "Out of memory"), abort();
lovrAssert(data, "Out of memory");
return data;
}
void* lovrRealloc(void* old, size_t size) {
void* data = realloc(old, size);
if (!data) fprintf(stderr, "Out of memory"), abort();
lovrAssert(data, "Out of memory");
return data;
}
@ -27,40 +30,8 @@ void lovrFree(void* data) {
free(data);
}
// Error handling
static LOVR_THREAD_LOCAL errorFn* lovrErrorCallback;
static LOVR_THREAD_LOCAL void* lovrErrorUserdata;
// Refcounting
void lovrSetErrorCallback(errorFn* callback, void* userdata) {
lovrErrorCallback = callback;
lovrErrorUserdata = userdata;
}
void lovrThrow(const char* format, ...) {
va_list args;
va_start(args, format);
lovrErrorCallback(lovrErrorUserdata, format, args);
va_end(args);
exit(EXIT_FAILURE);
}
// Logging
logFn* lovrLogCallback;
void* lovrLogUserdata;
void lovrSetLogCallback(logFn* callback, void* userdata) {
lovrLogCallback = callback;
lovrLogUserdata = userdata;
}
void lovrLog(int level, const char* tag, const char* format, ...) {
va_list args;
va_start(args, format);
lovrLogCallback(lovrLogUserdata, level, tag, format, args);
va_end(args);
}
// Refcounting; to be reference-counted the object must have uint ref field as struct's first member
#if ATOMIC_INT_LOCK_FREE != 2
#error "Lock-free integer atomics are not supported on this platform, but are required for refcounting"
#endif
@ -77,18 +48,118 @@ void lovrRelease(void* object, void (*destructor)(void*)) {
}
}
// Dynamic Array
// Default malloc-based allocator for arr_t (like realloc except well-defined when size is 0)
void* arr_alloc(void* data, size_t size) {
if (size > 0) {
return realloc(data, size);
} else {
free(data);
return NULL;
// Defer
typedef struct {
void (*fn)(void*);
void* arg;
} Closure;
static LOVR_THREAD_LOCAL struct {
Closure stack[16];
uint16_t releaseMask;
uint16_t errMask;
uint32_t top;
} defer;
uint32_t lovrDeferPush(void) {
return defer.top;
}
static void deferPop(uint32_t base, bool err) {
while (defer.top > base) {
uint32_t index = --defer.top;
Closure c = defer.stack[index];
if (err || (defer.errMask & (1u << index)) == 0) {
if (defer.releaseMask & (1u << index)) {
lovrRelease(c.arg, c.fn);
} else {
c.fn(c.arg);
}
}
}
}
void lovrDeferPop(uint32_t base) {
deferPop(base, false);
}
void lovrDefer(void (*fn)(void*), void* arg) {
lovrAssert(defer.top < COUNTOF(defer.stack), "Defer stack overflow!");
defer.releaseMask &= ~(1u << defer.top);
defer.errMask &= ~(1u << defer.top);
defer.stack[defer.top++] = (Closure) { fn, arg };
}
void lovrErrDefer(void (*fn)(void*), void* arg) {
lovrAssert(defer.top < COUNTOF(defer.stack), "Defer stack overflow!");
defer.releaseMask &= ~(1u << defer.top);
defer.errMask |= (1u << defer.top);
defer.stack[defer.top++] = (Closure) { fn, arg };
}
void lovrDeferRelease(void* object, void (*destructor)(void*)) {
lovrAssert(defer.top < COUNTOF(defer.stack), "Defer stack overflow!");
defer.releaseMask |= (1u << defer.top);
defer.errMask &= ~(1u << defer.top);
defer.stack[defer.top++] = (Closure) { destructor, object };
}
// Exceptions
typedef struct Handler {
struct Handler* prev;
uint32_t baseDefer;
void (*catch)(void* arg, const char* format, va_list args);
void* arg;
jmp_buf env;
} Handler;
static LOVR_THREAD_LOCAL Handler* lovrHandler;
void lovrTry(void (*fn)(void*), void* arg, void(*catch)(void*, const char*, va_list), void* catchArg) {
lovrHandler = &(Handler) {
.prev = lovrHandler,
.baseDefer = defer.top,
.catch = catch,
.arg = arg
};
if (setjmp(lovrHandler->env) == 0) {
fn(arg);
}
lovrHandler = lovrHandler->prev;
}
void lovrThrow(const char* format, ...) {
deferPop(lovrHandler->baseDefer, true);
va_list args;
va_start(args, format);
lovrHandler->catch(lovrHandler->arg, format, args);
va_end(args);
longjmp(lovrHandler->env, 1);
}
// Logging
static fn_log* lovrLogCallback;
static void* lovrLogUserdata;
void lovrSetLogCallback(fn_log* callback, void* userdata) {
lovrLogCallback = callback;
lovrLogUserdata = userdata;
}
void lovrLog(int level, const char* tag, const char* format, ...) {
va_list args;
va_start(args, format);
lovrLogCallback(lovrLogUserdata, level, tag, format, args);
va_end(args);
}
// Hashmap
static void map_rehash(map_t* map) {
map_t old = *map;
map->size <<= 1;
@ -155,6 +226,7 @@ void map_set(map_t* map, uint64_t hash, uint64_t value) {
// UTF-8
// https://github.com/starwing/luautf8
size_t utf8_decode(const char *s, const char *e, unsigned *pch) {
unsigned ch;

View File

@ -39,13 +39,22 @@ void* lovrCalloc(size_t size);
void* lovrRealloc(void* data, size_t size);
void lovrFree(void* data);
// Error handling
typedef void errorFn(void*, const char*, va_list);
void lovrSetErrorCallback(errorFn* callback, void* userdata);
// Refcounting (to be refcounted, a struct must have a uint32_t refcount as its first field)
void lovrRetain(void* ref);
void lovrRelease(void* ref, void (*destructor)(void*));
// Defer
uint32_t lovrDeferPush(void);
void lovrDeferPop(uint32_t base);
void lovrDefer(void (*fn)(void*), void* arg);
void lovrErrDefer(void (*fn)(void*), void* arg);
void lovrDeferRelease(void* ref, void (*destructor)(void*));
// Exceptions
void lovrTry(void (*fn)(void*), void* arg, void (*catch)(void*, const char*, va_list), void* catchArg);
LOVR_NORETURN void lovrThrow(const char* format, ...);
#define lovrAssert(c, ...) do { if (!(c)) { lovrThrow(__VA_ARGS__); } } while(0)
#define lovrUnreachable() lovrThrow("Unreachable")
#ifdef LOVR_UNCHECKED
#define lovrCheck(c, ...) ((void) 0)
#else
@ -53,11 +62,30 @@ LOVR_NORETURN void lovrThrow(const char* format, ...);
#endif
// Logging
typedef void logFn(void*, int, const char*, const char*, va_list);
typedef void fn_log(void*, int, const char*, const char*, va_list);
enum { LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR };
void lovrSetLogCallback(logFn* callback, void* userdata);
void lovrSetLogCallback(fn_log* callback, void* userdata);
void lovrLog(int level, const char* tag, const char* format, ...);
// Dynamic Array
#define arr_t(T) struct { T* data; size_t length, capacity; }
#define arr_init(a) (a)->data = NULL, (a)->length = 0, (a)->capacity = 0
#define arr_free(a) if ((a)->data) lovrFree((a)->data)
#define arr_reserve(a, n) _arr_reserve((void**) &((a)->data), n, &(a)->capacity, sizeof(*(a)->data))
#define arr_expand(a, n) arr_reserve(a, (a)->length + n)
#define arr_push(a, x) arr_reserve(a, (a)->length + 1), (a)->data[(a)->length] = x, (a)->length++
#define arr_pop(a) (a)->data[--(a)->length]
#define arr_append(a, p, n) arr_reserve(a, (a)->length + n), memcpy((a)->data + (a)->length, p, n * sizeof(*(p))), (a)->length += n
#define arr_splice(a, i, n) memmove((a)->data + (i), (a)->data + ((i) + n), ((a)->length - (i) - (n)) * sizeof(*(a)->data)), (a)->length -= n
#define arr_clear(a) (a)->length = 0
static inline void _arr_reserve(void** data, size_t n, size_t* capacity, size_t stride) {
if (*capacity >= n) return;
if (*capacity == 0) *capacity = 1;
while (*capacity < n) *capacity *= 2;
*data = lovrRealloc(*data, *capacity * stride);
}
// Hash function (FNV1a)
static inline uint64_t hash64(const void* data, size_t length) {
const uint8_t* bytes = (const uint8_t*) data;
@ -68,33 +96,6 @@ static inline uint64_t hash64(const void* data, size_t length) {
return hash;
}
// Refcounting
void lovrRetain(void* ref);
void lovrRelease(void* ref, void (*destructor)(void*));
// Dynamic Array
typedef void* arr_allocator(void* data, size_t size);
#define arr_t(T) struct { T* data; arr_allocator* alloc; size_t length, capacity; }
#define arr_init(a, allocator) (a)->data = NULL, (a)->length = 0, (a)->capacity = 0, (a)->alloc = allocator
#define arr_free(a) if ((a)->data) (a)->alloc((a)->data, 0)
#define arr_reserve(a, n) _arr_reserve((void**) &((a)->data), n, &(a)->capacity, sizeof(*(a)->data), (a)->alloc)
#define arr_expand(a, n) arr_reserve(a, (a)->length + n)
#define arr_push(a, x) arr_reserve(a, (a)->length + 1), (a)->data[(a)->length] = x, (a)->length++
#define arr_pop(a) (a)->data[--(a)->length]
#define arr_append(a, p, n) arr_reserve(a, (a)->length + n), memcpy((a)->data + (a)->length, p, n * sizeof(*(p))), (a)->length += n
#define arr_splice(a, i, n) memmove((a)->data + (i), (a)->data + ((i) + n), ((a)->length - (i) - (n)) * sizeof(*(a)->data)), (a)->length -= n
#define arr_clear(a) (a)->length = 0
void* arr_alloc(void* data, size_t size);
static inline void _arr_reserve(void** data, size_t n, size_t* capacity, size_t stride, arr_allocator* allocator) {
if (*capacity >= n) return;
if (*capacity == 0) *capacity = 1;
while (*capacity < n) *capacity *= 2;
*data = allocator(*data, *capacity * stride);
lovrAssert(*data, "Out of memory");
}
// Hashmap (does not support removal)
typedef struct {
uint64_t* hashes;