diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a5400f9..73625ab8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ option(LOVR_ENABLE_SYSTEM "Enable the system module" ON) option(LOVR_ENABLE_THREAD "Enable the thread module" ON) option(LOVR_ENABLE_TIMER "Enable the timer module" ON) +option(LOVR_USE_GLFW "Use GLFW for desktop windows" ON) option(LOVR_USE_LUAJIT "Use LuaJIT instead of Lua" ON) option(LOVR_USE_GLSLANG "Use glslang to compile GLSL shaders" ON) option(LOVR_USE_VULKAN "Use the Vulkan renderer" ON) @@ -78,7 +79,7 @@ if(NOT ANDROID AND LOVR_BUILD_SHARED) endif() # GLFW -if(NOT (EMSCRIPTEN OR ANDROID)) +if(LOVR_USE_GLFW AND NOT (EMSCRIPTEN OR ANDROID)) if(LOVR_SYSTEM_GLFW) pkg_search_module(GLFW REQUIRED glfw3) include_directories(${GLFW_INCLUDE_DIRS}) @@ -569,13 +570,16 @@ if(LOVR_ENABLE_SYSTEM) src/modules/system/system.c src/api/l_system.c ) + + if(LOVR_USE_GLFW) + target_compile_definitions(lovr PRIVATE LOVR_USE_GLFW) + endif() else() target_compile_definitions(lovr PRIVATE LOVR_DISABLE_SYSTEM) endif() if(LOVR_ENABLE_THREAD) target_sources(lovr PRIVATE - src/modules/thread/channel.c src/modules/thread/thread.c src/api/l_thread.c src/api/l_thread_channel.c diff --git a/README.md b/README.md index 2319844b..17a4355b 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,7 @@ Resources - [**Documentation**](https://lovr.org/docs): Guides, tutorials, examples, and API documentation. - [**FAQ**](https://lovr.org/docs/FAQ): Frequently Asked Questions. -- [**Slack Group**](https://lovr.org/slack): For general LÖVR discussion and support. -- [**Matrix Room**](https://matrix.to/#/!XVAslexgYDYQnYnZBP:matrix.org): Decentralized alternative to Slack. +- [**Matrix**](https://lovr.org/matrix): The LÖVR community for discussion and support. - [**Nightly Builds**](https://lovr.org/download/nightly): Nightly builds for Windows. - [**Compiling Guide**](https://lovr.org/docs/Compiling): Information on compiling LÖVR from source. - [**Contributing**](https://lovr.org/docs/Contributing): Guide for helping out with development 💜 diff --git a/Tupfile.lua b/Tupfile.lua index 3785899a..235f9aa9 100644 --- a/Tupfile.lua +++ b/Tupfile.lua @@ -5,6 +5,7 @@ config = { supercharge = false, sanitize = false, strict = true, + glfw = true, luajit = false, glslang = true, modules = { @@ -221,8 +222,9 @@ else tup.rule('.obj/lua/*.o', '^ LD %o^ $(cc) $(flags) -o %o %f $(lua_lflags)', lib('lua')) end -if target == 'win32' or target == 'macos' or target == 'linux' then +if config.glfw and (target == 'win32' or target == 'macos' or target == 'linux') then cflags += '-Ideps/glfw/include' + cflags += '-DLOVR_USE_GLFW' lflags += '-lglfw' glfw_cflags += '-fPIC' diff --git a/src/api/api.c b/src/api/api.c index 815d2684..74047482 100644 --- a/src/api/api.c +++ b/src/api/api.c @@ -462,7 +462,8 @@ void luax_optcolor(lua_State* L, int index, float color[4]) { color[3] = luax_optfloat(L, -1, 1.); lua_pop(L, 4); break; - case LUA_TUSERDATA: { + case LUA_TUSERDATA: + case LUA_TLIGHTUSERDATA: { VectorType type; float* v = luax_tovector(L, index, &type); if (type == V_VEC3) { @@ -473,8 +474,7 @@ void luax_optcolor(lua_State* L, int index, float color[4]) { memcpy(color, v, 4 * sizeof(float)); break; } - /* fallthrough */ - } + } /* fallthrough */ default: lovrThrow("Expected nil, number, table, vec3, or vec4 for color value"); } } diff --git a/src/api/l_event.c b/src/api/l_event.c index 2df5ac4d..93bfef91 100644 --- a/src/api/l_event.c +++ b/src/api/l_event.c @@ -141,6 +141,7 @@ static int nextEvent(lua_State* L) { luax_pushtype(L, Thread, event.data.thread.thread); lua_pushstring(L, event.data.thread.error); lovrRelease(event.data.thread.thread, lovrThreadDestroy); + free(event.data.thread.error); return 3; #endif diff --git a/src/api/l_graphics.c b/src/api/l_graphics.c index 1a854d89..f682ec28 100644 --- a/src/api/l_graphics.c +++ b/src/api/l_graphics.c @@ -494,7 +494,7 @@ static Canvas luax_checkcanvas(lua_State* L, int index) { case LUA_TUSERDATA: canvas.depth.texture = luax_checktype(L, -1, Texture); break; case LUA_TTABLE: lua_getfield(L, -1, "format"); - canvas.depth.format = lua_isnil(L, -1) ? canvas.depth.format : luax_checkenum(L, -1, TextureFormat, NULL); + canvas.depth.format = lua_isnil(L, -1) ? canvas.depth.format : (uint32_t) luax_checkenum(L, -1, TextureFormat, NULL); lua_pop(L, 1); lua_getfield(L, -1, "texture"); @@ -981,12 +981,12 @@ static int l_lovrGraphicsNewTexture(lua_State* L) { if (lua_istable(L, index)) { lua_getfield(L, index, "type"); - info.type = lua_isnil(L, -1) ? info.type : luax_checkenum(L, -1, TextureType, NULL); + info.type = lua_isnil(L, -1) ? info.type : (uint32_t) luax_checkenum(L, -1, TextureType, NULL); lua_pop(L, 1); if (info.imageCount == 0) { lua_getfield(L, index, "format"); - info.format = lua_isnil(L, -1) ? info.format : luax_checkenum(L, -1, TextureFormat, NULL); + info.format = lua_isnil(L, -1) ? info.format : (uint32_t) luax_checkenum(L, -1, TextureFormat, NULL); lua_pop(L, 1); lua_getfield(L, index, "samples"); @@ -1428,6 +1428,7 @@ static int l_lovrGraphicsNewFont(lua_State* L) { lovrRelease(blob, lovrBlobDestroy); } else { info.spread = luaL_optnumber(L, 2, info.spread); + lovrRetain(info.rasterizer); } Font* font = lovrFontCreate(&info); diff --git a/src/api/l_graphics_pass.c b/src/api/l_graphics_pass.c index 332c4aec..ac6c3701 100644 --- a/src/api/l_graphics_pass.c +++ b/src/api/l_graphics_pass.c @@ -555,12 +555,13 @@ static void luax_readvertices(lua_State* L, int index, float* vertices, uint32_t *vertices++ = luax_tofloat(L, -1); lua_pop(L, 1); } - } else if (innerType == LUA_TUSERDATA) { + } else if (innerType == LUA_TUSERDATA || innerType == LUA_TLIGHTUSERDATA) { for (uint32_t i = 0; i < count; i++) { lua_rawgeti(L, index, i + 1); - vec3_init(vertices, luax_checkvector(L, -1, V_VEC3, NULL)); - lua_pop(L, 1); + float* v = luax_checkvector(L, -1, V_VEC3, NULL); + memcpy(vertices, v, 3 * sizeof(float)); vertices += 3; + lua_pop(L, 1); } } break; @@ -644,7 +645,7 @@ static int l_lovrPassSphere(lua_State* L) { return 0; } -static bool luax_checkendpoints(lua_State* L, int index, float transform[16]) { +static bool luax_checkendpoints(lua_State* L, int index, float transform[16], bool center) { float *v, *u; VectorType t1, t2; if ((v = luax_tovector(L, index + 0, &t1)) == NULL || t1 != V_VEC3) return false; @@ -658,7 +659,11 @@ static bool luax_checkendpoints(lua_State* L, int index, float transform[16]) { vec3_normalize(direction); quat_between(orientation, forward, direction); mat4_identity(transform); - mat4_translate(transform, (v[0] + u[0]) / 2.f, (v[1] + u[1]) / 2.f, (v[2] + u[2]) / 2.f); + if (center) { + mat4_translate(transform, (v[0] + u[0]) / 2.f, (v[1] + u[1]) / 2.f, (v[2] + u[2]) / 2.f); + } else { + mat4_translate(transform, v[0], v[1], v[2]); + } mat4_rotateQuat(transform, orientation); mat4_scale(transform, radius, radius, length); return true; @@ -667,7 +672,7 @@ static bool luax_checkendpoints(lua_State* L, int index, float transform[16]) { static int l_lovrPassCylinder(lua_State* L) { Pass* pass = luax_checktype(L, 1, Pass); float transform[16]; - int index = luax_checkendpoints(L, 2, transform) ? 5 : luax_readmat4(L, 2, transform, -2); + int index = luax_checkendpoints(L, 2, transform, true) ? 5 : luax_readmat4(L, 2, transform, -2); bool capped = lua_isnoneornil(L, index) ? true : lua_toboolean(L, index++); float angle1 = luax_optfloat(L, index++, 0.f); float angle2 = luax_optfloat(L, index++, 2.f * (float) M_PI); @@ -679,7 +684,7 @@ static int l_lovrPassCylinder(lua_State* L) { static int l_lovrPassCone(lua_State* L) { Pass* pass = luax_checktype(L, 1, Pass); float transform[16]; - int index = luax_readmat4(L, 2, transform, -2); + int index = luax_checkendpoints(L, 2, transform, false) ? 5 : luax_readmat4(L, 2, transform, -2); uint32_t segments = luax_optu32(L, index, 64); lovrPassCone(pass, transform, segments); return 0; @@ -688,7 +693,7 @@ static int l_lovrPassCone(lua_State* L) { static int l_lovrPassCapsule(lua_State* L) { Pass* pass = luax_checktype(L, 1, Pass); float transform[16]; - int index = luax_checkendpoints(L, 2, transform) ? 5 : luax_readmat4(L, 2, transform, -2); + int index = luax_checkendpoints(L, 2, transform, true) ? 5 : luax_readmat4(L, 2, transform, -2); uint32_t segments = luax_optu32(L, index, 32); lovrPassCapsule(pass, transform, segments); return 0; diff --git a/src/api/l_thread.c b/src/api/l_thread.c index e9714b36..ae19b88f 100644 --- a/src/api/l_thread.c +++ b/src/api/l_thread.c @@ -1,20 +1,13 @@ #include "api.h" +#include "data/blob.h" #include "event/event.h" #include "thread/thread.h" -#include "thread/channel.h" #include "util.h" #include #include #include -static int threadRunner(void* data) { - Thread* thread = (Thread*) data; - - lovrRetain(thread); - mtx_lock(&thread->lock); - thread->running = true; - mtx_unlock(&thread->lock); - +static char* threadRunner(Thread* thread, Blob* body, Variant* arguments, uint32_t argumentCount) { lua_State* L = luaL_newstate(); luaL_openlibs(L); luax_preload(L); @@ -23,42 +16,29 @@ static int threadRunner(void* data) { lua_pushcfunction(L, luax_getstack); int errhandler = lua_gettop(L); - if (!luaL_loadbuffer(L, thread->body->data, thread->body->size, "thread")) { - for (uint32_t i = 0; i < thread->argumentCount; i++) { - luax_pushvariant(L, &thread->arguments[i]); + 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, thread->argumentCount, 0, errhandler)) { - mtx_lock(&thread->lock); - thread->running = false; - mtx_unlock(&thread->lock); - lovrRelease(thread, lovrThreadDestroy); - lua_close(L); - return 0; + if (!lua_pcall(L, argumentCount, 0, errhandler)) { + return NULL; } } - mtx_lock(&thread->lock); - // Error handling size_t length; - const char* error = lua_tolstring(L, -1, &length); + const char* message = lua_tolstring(L, -1, &length); + char* error = message ? malloc(length + 1) : NULL; + if (error) { - thread->error = malloc(length + 1); - if (thread->error) { - memcpy(thread->error, error, length + 1); - lovrEventPush((Event) { - .type = EVENT_THREAD_ERROR, - .data.thread = { thread, thread->error } - }); - } + memcpy(error, message, length + 1); + lua_close(L); + return error; } - thread->running = false; - mtx_unlock(&thread->lock); - lovrRelease(thread, lovrThreadDestroy); lua_close(L); - return 1; + return NULL; } static int l_lovrThreadNewThread(lua_State* L) { diff --git a/src/api/l_thread_channel.c b/src/api/l_thread_channel.c index 19b1336f..c95147de 100644 --- a/src/api/l_thread_channel.c +++ b/src/api/l_thread_channel.c @@ -1,5 +1,5 @@ #include "api.h" -#include "thread/channel.h" +#include "thread/thread.h" #include "event/event.h" #include "util.h" #include diff --git a/src/api/l_thread_thread.c b/src/api/l_thread_thread.c index 109e9804..bc461cb4 100644 --- a/src/api/l_thread_thread.c +++ b/src/api/l_thread_thread.c @@ -1,4 +1,5 @@ #include "api.h" +#include "event/event.h" #include "thread/thread.h" #include "util.h" @@ -32,7 +33,8 @@ static int l_lovrThreadGetError(lua_State* L) { static int l_lovrThreadIsRunning(lua_State* L) { Thread* thread = luax_checktype(L, 1, Thread); - lua_pushboolean(L, thread->running); + bool running = lovrThreadIsRunning(thread); + lua_pushboolean(L, running); return 1; } diff --git a/src/core/gpu_vk.c b/src/core/gpu_vk.c index 19e80652..bba6c970 100644 --- a/src/core/gpu_vk.c +++ b/src/core/gpu_vk.c @@ -2049,15 +2049,15 @@ bool gpu_init(gpu_config* config) { enable->largePoints = supports->largePoints; // Optional features (currently always enabled when supported) - config->features->textureBC = enable->textureCompressionBC = supports->textureCompressionBC; - config->features->textureASTC = enable->textureCompressionASTC_LDR = supports->textureCompressionASTC_LDR; - config->features->wireframe = enable->fillModeNonSolid = supports->fillModeNonSolid; - config->features->depthClamp = enable->depthClamp = supports->depthClamp; - config->features->indirectDrawFirstInstance = enable->drawIndirectFirstInstance = supports->drawIndirectFirstInstance; - config->features->shaderTally = enable->pipelineStatisticsQuery = supports->pipelineStatisticsQuery; - config->features->float64 = enable->shaderFloat64 = supports->shaderFloat64; - config->features->int64 = enable->shaderInt64 = supports->shaderInt64; - config->features->int16 = enable->shaderInt16 = supports->shaderInt16; + config->features->textureBC = (enable->textureCompressionBC = supports->textureCompressionBC); + config->features->textureASTC = (enable->textureCompressionASTC_LDR = supports->textureCompressionASTC_LDR); + config->features->wireframe = (enable->fillModeNonSolid = supports->fillModeNonSolid); + config->features->depthClamp = (enable->depthClamp = supports->depthClamp); + config->features->indirectDrawFirstInstance = (enable->drawIndirectFirstInstance = supports->drawIndirectFirstInstance); + config->features->shaderTally = (enable->pipelineStatisticsQuery = supports->pipelineStatisticsQuery); + config->features->float64 = (enable->shaderFloat64 = supports->shaderFloat64); + config->features->int64 = (enable->shaderInt64 = supports->shaderInt64); + config->features->int16 = (enable->shaderInt16 = supports->shaderInt16); // Formats for (uint32_t i = 0; i < GPU_FORMAT_COUNT; i++) { @@ -3070,7 +3070,9 @@ static void nickname(void* handle, VkObjectType type, const char* name) { static bool vcheck(VkResult result, const char* message) { if (result >= 0) return true; if (!state.config.callback) return false; -#define CASE(x) case x: state.config.callback(state.config.userdata, "Vulkan error: " #x, true); break; + + const char* errorCode = ""; +#define CASE(x) case x: errorCode = " (" #x ")"; break; switch (result) { CASE(VK_ERROR_OUT_OF_HOST_MEMORY); CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY); @@ -3088,8 +3090,21 @@ static bool vcheck(VkResult result, const char* message) { default: break; } #undef CASE - state.config.callback(state.config.userdata, message, true); - return false; + + char string[128]; + size_t length1 = strlen(message); + size_t length2 = strlen(errorCode); + + if (length1 + length2 >= sizeof(string)) { + state.config.callback(state.config.userdata, message, true); + return false; + } else { + memcpy(string, message, length1); + memcpy(string + length1, errorCode, length2); + string[length1 + length2] = '\0'; + state.config.callback(state.config.userdata, string, true); + return false; + } } static bool check(bool condition, const char* message) { diff --git a/src/core/os_android.c b/src/core/os_android.c index e37073e8..2ae4aa07 100644 --- a/src/core/os_android.c +++ b/src/core/os_android.c @@ -170,6 +170,22 @@ bool os_init() { } void os_destroy() { + // There are two ways a quit can happen, which need to be handled slightly differently: + // - If the system tells us to quit, we get an event with APP_CMD_DESTROY. In response we push a + // QUIT event to lovr.event and main will eventually exit cleanly. No other teardown necessary. + // - If the app exits manually (e.g. lovr.event.quit), then Android thinks we're still running and + // the app is still in APP_CMD_RESUME. We need to tell it to exit with ANativeActivity_Finish + // and poll for events until the app state changes, otherwise the app is left in a broken state. + if (state.app->activityState == APP_CMD_RESUME) { + state.onQuit = NULL; + state.onKeyboardEvent = NULL; + state.onTextEvent = NULL; + state.onPermissionEvent = NULL; + ANativeActivity_finish(state.app->activity); + while (!state.app->destroyRequested) { + os_poll_events(); + } + } memset(&state, 0, sizeof(state)); } diff --git a/src/core/os_glfw.h b/src/core/os_glfw.h index 03d6811d..bd200c08 100644 --- a/src/core/os_glfw.h +++ b/src/core/os_glfw.h @@ -1,3 +1,77 @@ +#ifndef LOVR_USE_GLFW + +void os_destroy() { + // +} + +void os_poll_events() { + // +} + +bool os_window_open(const os_window_config* config) { + return false; +} + +bool os_window_is_open() { + return false; +} + +void os_window_get_size(uint32_t* width, uint32_t* height) { + *width = *height = 0; +} + +void os_window_get_fbsize(uint32_t* width, uint32_t* height) { + *width = *height = 0; +} + +void os_on_quit(fn_quit* callback) { + // +} + +void os_on_focus(fn_focus* callback) { + // +} + +void os_on_resize(fn_resize* callback) { + // +} + +void os_on_key(fn_key* callback) { + // +} + +void os_on_text(fn_text* callback) { + // +} + +void os_get_mouse_position(double* x, double* y) { + *x = *y = 0.; +} + +void os_set_mouse_mode(os_mouse_mode mode) { + // +} + +bool os_is_mouse_down(os_mouse_button button) { + return false; +} + +bool os_is_key_down(os_key key) { + return false; +} + +#ifdef LOVR_VK +const char** os_vk_get_instance_extensions(uint32_t* count) { + return *count = 0, NULL; +} + +uint32_t os_vk_create_surface(void* instance, void** surface) { + return -13; // VK_ERROR_UNKNOWN +} +#endif + +#else + #include #ifdef LOVR_VK @@ -180,6 +254,10 @@ static int convertKey(os_key key) { } } +void os_destroy() { + glfwTerminate(); +} + void os_poll_events() { if (glfwState.window) { glfwPollEvents(); @@ -203,12 +281,7 @@ bool os_window_open(const os_window_config* config) { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); #endif - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, config->resizable); - glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); GLFWmonitor* monitor = glfwGetPrimaryMonitor(); const GLFWvidmode* mode = glfwGetVideoMode(monitor); @@ -340,3 +413,5 @@ uint32_t os_vk_create_surface(void* instance, void** surface) { OS_DLL_EXPORT GLFWwindow* os_get_glfw_window(void) { return glfwState.window; } + +#endif diff --git a/src/core/os_linux.c b/src/core/os_linux.c index 6b18a2dc..e3639291 100644 --- a/src/core/os_linux.c +++ b/src/core/os_linux.c @@ -14,10 +14,6 @@ bool os_init() { return true; } -void os_destroy() { - glfwTerminate(); -} - const char* os_get_name() { return "Linux"; } diff --git a/src/core/os_macos.c b/src/core/os_macos.c index b676666b..78f86cbd 100644 --- a/src/core/os_macos.c +++ b/src/core/os_macos.c @@ -26,11 +26,6 @@ bool os_init() { return true; } -void os_destroy() { - glfwTerminate(); - memset(&state, 0, sizeof(state)); -} - const char* os_get_name() { return "macOS"; } diff --git a/src/core/os_win32.c b/src/core/os_win32.c index a4565ebf..29772659 100644 --- a/src/core/os_win32.c +++ b/src/core/os_win32.c @@ -62,10 +62,6 @@ bool os_init() { return true; } -void os_destroy() { - glfwTerminate(); -} - const char* os_get_name() { return "Windows"; } diff --git a/src/modules/data/image.c b/src/modules/data/image.c index 4285d1b2..c97ffd80 100644 --- a/src/modules/data/image.c +++ b/src/modules/data/image.c @@ -1130,30 +1130,30 @@ static Image* loadKTX2(Blob* blob) { case 126: image->format = FORMAT_D32F; break; case 129: image->format = FORMAT_D24S8; break; case 130: image->format = FORMAT_D32FS8; break; - case 132: image->flags |= IMAGE_SRGB; case 131: image->format = FORMAT_BC1; break; - case 136: image->flags |= IMAGE_SRGB; case 135: image->format = FORMAT_BC2; break; - case 138: image->flags |= IMAGE_SRGB; case 137: image->format = FORMAT_BC3; break; + case 132: image->flags |= IMAGE_SRGB; /* fallthrough */ case 131: image->format = FORMAT_BC1; break; + case 136: image->flags |= IMAGE_SRGB; /* fallthrough */ case 135: image->format = FORMAT_BC2; break; + case 138: image->flags |= IMAGE_SRGB; /* fallthrough */ case 137: image->format = FORMAT_BC3; break; case 139: image->format = FORMAT_BC4U; break; case 140: image->format = FORMAT_BC4S; break; case 141: image->format = FORMAT_BC5U; break; case 142: image->format = FORMAT_BC5S; break; case 143: image->format = FORMAT_BC6UF; break; case 144: image->format = FORMAT_BC6SF; break; - case 146: image->flags |= IMAGE_SRGB; case 145: image->format = FORMAT_BC7; break; - case 158: image->flags |= IMAGE_SRGB; case 157: image->format = FORMAT_ASTC_4x4; break; - case 160: image->flags |= IMAGE_SRGB; case 159: image->format = FORMAT_ASTC_5x4; break; - case 162: image->flags |= IMAGE_SRGB; case 161: image->format = FORMAT_ASTC_5x5; break; - case 164: image->flags |= IMAGE_SRGB; case 163: image->format = FORMAT_ASTC_6x5; break; - case 166: image->flags |= IMAGE_SRGB; case 165: image->format = FORMAT_ASTC_6x6; break; - case 168: image->flags |= IMAGE_SRGB; case 167: image->format = FORMAT_ASTC_8x5; break; - case 170: image->flags |= IMAGE_SRGB; case 169: image->format = FORMAT_ASTC_8x6; break; - case 172: image->flags |= IMAGE_SRGB; case 171: image->format = FORMAT_ASTC_8x8; break; - case 174: image->flags |= IMAGE_SRGB; case 173: image->format = FORMAT_ASTC_10x5; break; - case 176: image->flags |= IMAGE_SRGB; case 175: image->format = FORMAT_ASTC_10x6; break; - case 178: image->flags |= IMAGE_SRGB; case 177: image->format = FORMAT_ASTC_10x8; break; - case 180: image->flags |= IMAGE_SRGB; case 179: image->format = FORMAT_ASTC_10x10; break; - case 182: image->flags |= IMAGE_SRGB; case 181: image->format = FORMAT_ASTC_12x10; break; - case 184: image->flags |= IMAGE_SRGB; case 183: image->format = FORMAT_ASTC_12x12; break; + case 146: image->flags |= IMAGE_SRGB; /* fallthrough */ case 145: image->format = FORMAT_BC7; break; + case 158: image->flags |= IMAGE_SRGB; /* fallthrough */ case 157: image->format = FORMAT_ASTC_4x4; break; + case 160: image->flags |= IMAGE_SRGB; /* fallthrough */ case 159: image->format = FORMAT_ASTC_5x4; break; + case 162: image->flags |= IMAGE_SRGB; /* fallthrough */ case 161: image->format = FORMAT_ASTC_5x5; break; + case 164: image->flags |= IMAGE_SRGB; /* fallthrough */ case 163: image->format = FORMAT_ASTC_6x5; break; + case 166: image->flags |= IMAGE_SRGB; /* fallthrough */ case 165: image->format = FORMAT_ASTC_6x6; break; + case 168: image->flags |= IMAGE_SRGB; /* fallthrough */ case 167: image->format = FORMAT_ASTC_8x5; break; + case 170: image->flags |= IMAGE_SRGB; /* fallthrough */ case 169: image->format = FORMAT_ASTC_8x6; break; + case 172: image->flags |= IMAGE_SRGB; /* fallthrough */ case 171: image->format = FORMAT_ASTC_8x8; break; + case 174: image->flags |= IMAGE_SRGB; /* fallthrough */ case 173: image->format = FORMAT_ASTC_10x5; break; + case 176: image->flags |= IMAGE_SRGB; /* fallthrough */ case 175: image->format = FORMAT_ASTC_10x6; break; + case 178: image->flags |= IMAGE_SRGB; /* fallthrough */ case 177: image->format = FORMAT_ASTC_10x8; break; + case 180: image->flags |= IMAGE_SRGB; /* fallthrough */ case 179: image->format = FORMAT_ASTC_10x10; break; + case 182: image->flags |= IMAGE_SRGB; /* fallthrough */ case 181: image->format = FORMAT_ASTC_12x10; break; + case 184: image->flags |= IMAGE_SRGB; /* fallthrough */ case 183: image->format = FORMAT_ASTC_12x12; break; default: lovrThrow("KTX file uses an unsupported image format"); } diff --git a/src/modules/event/event.c b/src/modules/event/event.c index 9aa37be2..72917a3b 100644 --- a/src/modules/event/event.c +++ b/src/modules/event/event.c @@ -53,6 +53,12 @@ void lovrEventPush(Event event) { #ifndef LOVR_DISABLE_THREAD if (event.type == EVENT_THREAD_ERROR) { lovrRetain(event.data.thread.thread); + size_t length = strlen(event.data.thread.error); + char* copy = malloc(length + 1); + lovrAssert(copy, "Out of memory"); + memcpy(copy, event.data.thread.error, length); + copy[length] = '\0'; + event.data.thread.error = copy; } #endif diff --git a/src/modules/filesystem/filesystem.c b/src/modules/filesystem/filesystem.c index ebd86738..3073e292 100644 --- a/src/modules/filesystem/filesystem.c +++ b/src/modules/filesystem/filesystem.c @@ -441,42 +441,101 @@ void lovrFilesystemSetRequirePath(const char* requirePath) { // Archive: dir -static bool dir_resolve(char* buffer, Archive* archive, const char* path) { - char innerBuffer[LOVR_PATH_MAX]; - size_t length = strlen(path); - if (length >= sizeof(innerBuffer)) return false; - length = normalize(innerBuffer, path, length); - path = innerBuffer; +enum { + PATH_INVALID, + PATH_VIRTUAL, + PATH_PHYSICAL +}; - if (archive->mountpoint) { - if (strncmp(path, strpool_resolve(&archive->strings, archive->mountpoint), archive->mountpointLength)) { - return false; - } else { - path += archive->mountpointLength; - length -= archive->mountpointLength; +static int dir_resolve(Archive* archive, char* buffer, const char* path) { + char normalized[LOVR_PATH_MAX]; + + // Normalize the path + size_t length = strlen(path); + if (length >= sizeof(normalized)) return PATH_INVALID; + length = normalize(normalized, path, length); + + // Compare each component of normalized path and mountpoint + if (archive->mountpointLength > 0) { + const char* mountpoint = strpool_resolve(&archive->strings, archive->mountpoint); + size_t mountpointLength = archive->mountpointLength; + + for (;;) { + char* slash = strchr(mountpoint, '/'); + size_t sublength = slash ? slash - mountpoint : mountpointLength; + + // If the path is empty but there was still stuff in the mountpoint, it's a virtual directory + if (length == 0) { + // Return child directory's name for convenience in getDirectoryItems + memcpy(buffer, mountpoint, sublength); + buffer[sublength] = '\0'; + return PATH_VIRTUAL; + } + + // Check for paths that don't match this component of the mountpoint + if (length < sublength || strncmp(path, mountpoint, sublength)) { + return PATH_INVALID; + } + + // If the path matched, make sure there's a slash after the match + if (length > sublength && path[sublength] != '/') { + return PATH_INVALID; + } + + // Strip this component off of the path + if (length == sublength) { + path += sublength; + length -= sublength; + } else { + path += sublength + 1; + length -= sublength + 1; + } + + // Strip this component off of the mountpoint, if mountpoint is empty then we're done + if (mountpointLength > sublength) { + mountpoint += sublength + 1; + mountpointLength -= sublength + 1; + } else { + break; + } } } - return concat(buffer, strpool_resolve(&archive->strings, archive->path), archive->pathLength, path, length); + // Concat archive path and normalized path (without mountpoint), return full path + if (!concat(buffer, strpool_resolve(&archive->strings, archive->path), archive->pathLength, path, length)) { + return PATH_INVALID; + } + + return PATH_PHYSICAL; } static bool dir_stat(Archive* archive, const char* path, FileInfo* info) { char resolved[LOVR_PATH_MAX]; - return dir_resolve(resolved, archive, path) && fs_stat(resolved, info); + switch (dir_resolve(archive, resolved, path)) { + default: + case PATH_INVALID: return false; + case PATH_VIRTUAL: return fs_stat(strpool_resolve(&archive->strings, archive->path), info); + case PATH_PHYSICAL: return fs_stat(resolved, info); + } } static void dir_list(Archive* archive, const char* path, fs_list_cb callback, void* context) { char resolved[LOVR_PATH_MAX]; - if (dir_resolve(resolved, archive, path)) { - fs_list(resolved, callback, context); + switch (dir_resolve(archive, resolved, path)) { + case PATH_INVALID: return; + case PATH_VIRTUAL: callback(context, resolved); return; + case PATH_PHYSICAL: fs_list(resolved, callback, context); return; } } static bool dir_read(Archive* archive, const char* path, size_t bytes, size_t* bytesRead, void** data) { char resolved[LOVR_PATH_MAX]; - fs_handle file; + if (dir_resolve(archive, resolved, path) != PATH_PHYSICAL) { + return false; + } - if (!dir_resolve(resolved, archive, path) || !fs_open(resolved, OPEN_READ, &file)) { + fs_handle file; + if (!fs_open(resolved, OPEN_READ, &file)) { return false; } @@ -531,7 +590,7 @@ static zip_node* zip_lookup(Archive* archive, const char* path) { size_t length = strlen(path); if (length >= sizeof(buffer)) return NULL; length = normalize(buffer, path, length); - uint64_t hash = hash64(buffer, length); + uint64_t hash = length ? hash64(buffer, length) : 0; uint64_t index = map_get(&archive->lookup, hash); return index == MAP_NIL ? NULL : &archive->nodes.data[index]; } @@ -640,7 +699,10 @@ static bool zip_init(Archive* archive, const char* filename, const char* mountpo } mountpointLength = normalize(path, mountpoint, mountpointLength); - path[mountpointLength++] = '/'; + + if (mountpointLength > 0) { + path[mountpointLength++] = '/'; + } } // Simple root normalization (only strips leading/trailing slashes, sorry) @@ -696,7 +758,7 @@ static bool zip_init(Archive* archive, const char* filename, const char* mountpo // Keep chopping off path segments, building up a tree of paths // We can stop early if we reach a path that has already been indexed // Also add individual path segments to the string pool, for zip_list - while (length != SIZE_MAX) { + for (;;) { uint64_t hash = hash64(path, length); uint64_t index = map_get(&archive->lookup, hash); @@ -720,6 +782,16 @@ static bool zip_init(Archive* archive, const char* filename, const char* mountpo } archive->nodes.data[index].filename = strpool_append(&archive->strings, path + length, slash - length); + + // Root node + if (length == 0) { + index = archive->nodes.length; + map_set(&archive->lookup, 0, index); + arr_push(&archive->nodes, node); + archive->nodes.data[index].filename = strpool_append(&archive->strings, path, 0); + break; + } + slash = --length; } } diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 77b18b48..f21b4e16 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -418,6 +418,7 @@ static void processReadbacks(void); static size_t getLayout(gpu_slot* slots, uint32_t count); static gpu_bundle* getBundle(size_t layout); static gpu_texture* getScratchTexture(gpu_texture_info* info); +static bool isDepthFormat(TextureFormat format); static uint32_t measureTexture(TextureFormat format, uint32_t w, uint32_t h, uint32_t d); static void checkTextureBounds(const TextureInfo* info, uint32_t offset[4], uint32_t extent[3]); static void mipmapTexture(gpu_stream* stream, Texture* texture, uint32_t base, uint32_t count); @@ -3300,6 +3301,7 @@ Pass* lovrGraphicsGetPass(PassInfo* info) { for (uint32_t i = 0; i < canvas->count; i++) { const TextureInfo* texture = &canvas->textures[i]->info; bool renderable = texture->format == GPU_FORMAT_SURFACE || (state.features.formats[texture->format] & GPU_FEATURE_RENDER); + lovrCheck(!isDepthFormat(texture->format), "Unable to use a depth texture as a color target"); lovrCheck(renderable, "This GPU does not support rendering to the texture format used by color target #%d", i + 1); lovrCheck(texture->usage & TEXTURE_RENDER, "Texture must be created with the 'render' flag to render to it"); lovrCheck(texture->width == t->width, "Render pass texture sizes must match"); @@ -3313,6 +3315,7 @@ Pass* lovrGraphicsGetPass(PassInfo* info) { if (depth->texture || depth->format) { TextureFormat format = depth->texture ? depth->texture->info.format : depth->format; bool renderable = state.features.formats[format] & GPU_FEATURE_RENDER; + lovrCheck(isDepthFormat(format), "Unable to use a color texture as a depth target"); lovrCheck(renderable, "This GPU does not support depth buffers with this texture format"); if (depth->texture) { const TextureInfo* texture = &depth->texture->info; @@ -4338,6 +4341,8 @@ void lovrPassPoints(Pass* pass, uint32_t count, float** points) { } void lovrPassLine(Pass* pass, uint32_t count, float** points) { + lovrCheck(count >= 2, "Need at least 2 points to make a line"); + uint16_t* indices; lovrPassDraw(pass, &(Draw) { @@ -5661,6 +5666,10 @@ static gpu_texture* getScratchTexture(gpu_texture_info* info) { return scratch->texture; } +static bool isDepthFormat(TextureFormat format) { + return format == FORMAT_D16 || format == FORMAT_D32F || format == FORMAT_D24S8 || format == FORMAT_D32FS8; +} + // Returns number of bytes of a 3D texture region of a given format static uint32_t measureTexture(TextureFormat format, uint32_t w, uint32_t h, uint32_t d) { switch (format) { diff --git a/src/modules/thread/channel.c b/src/modules/thread/channel.c deleted file mode 100644 index 41d26280..00000000 --- a/src/modules/thread/channel.c +++ /dev/null @@ -1,151 +0,0 @@ -#include "thread/channel.h" -#include "event/event.h" -#include "util.h" -#include "lib/tinycthread/tinycthread.h" -#include -#include -#include - -struct Channel { - uint32_t ref; - mtx_t lock; - cnd_t cond; - arr_t(Variant) messages; - size_t head; - uint64_t sent; - uint64_t received; - uint64_t hash; -}; - -Channel* lovrChannelCreate(uint64_t hash) { - Channel* channel = calloc(1, sizeof(Channel)); - lovrAssert(channel, "Out of memory"); - channel->ref = 1; - arr_init(&channel->messages, arr_alloc); - mtx_init(&channel->lock, mtx_plain | mtx_timed); - cnd_init(&channel->cond); - channel->hash = hash; - return channel; -} - -void lovrChannelDestroy(void* ref) { - Channel* channel = ref; - lovrChannelClear(channel); - arr_free(&channel->messages); - mtx_destroy(&channel->lock); - cnd_destroy(&channel->cond); - free(channel); -} - -bool lovrChannelPush(Channel* channel, Variant* variant, double timeout, uint64_t* id) { - mtx_lock(&channel->lock); - if (channel->messages.length == 0) { - lovrRetain(channel); - } - arr_push(&channel->messages, *variant); - *id = ++channel->sent; - cnd_broadcast(&channel->cond); - - if (isnan(timeout) || timeout < 0) { - mtx_unlock(&channel->lock); - return false; - } - - while (channel->received < *id && timeout >= 0) { - if (isinf(timeout)) { - cnd_wait(&channel->cond, &channel->lock); - } else { - struct timespec start; - struct timespec until; - struct timespec stop; - timespec_get(&start, TIME_UTC); - double whole, fraction; - fraction = modf(timeout, &whole); - until.tv_sec = start.tv_sec + whole; - until.tv_nsec = start.tv_nsec + fraction * 1e9; - cnd_timedwait(&channel->cond, &channel->lock, &until); - timespec_get(&stop, TIME_UTC); - timeout -= (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec) / 1e9; - } - } - - bool read = channel->received >= *id; - mtx_unlock(&channel->lock); - return read; -} - -bool lovrChannelPop(Channel* channel, Variant* variant, double timeout) { - mtx_lock(&channel->lock); - - do { - if (channel->head < channel->messages.length) { - *variant = channel->messages.data[channel->head++]; - if (channel->head == channel->messages.length) { - channel->head = channel->messages.length = 0; - lovrRelease(channel, lovrChannelDestroy); - } - channel->received++; - cnd_broadcast(&channel->cond); - mtx_unlock(&channel->lock); - return true; - } else if (isnan(timeout) || timeout < 0) { - mtx_unlock(&channel->lock); - return false; - } - - if (isinf(timeout)) { - cnd_wait(&channel->cond, &channel->lock); - } else { - struct timespec start; - struct timespec until; - struct timespec stop; - timespec_get(&start, TIME_UTC); - double whole, fraction; - fraction = modf(timeout, &whole); - until.tv_sec = start.tv_sec + whole; - until.tv_nsec = start.tv_nsec + fraction * 1e9; - cnd_timedwait(&channel->cond, &channel->lock, &until); - timespec_get(&stop, TIME_UTC); - timeout -= (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec) / (double) 1e9; - } - } while (1); -} - -bool lovrChannelPeek(Channel* channel, Variant* variant) { - mtx_lock(&channel->lock); - - if (channel->head < channel->messages.length) { - *variant = channel->messages.data[channel->head]; - mtx_unlock(&channel->lock); - return true; - } - - mtx_unlock(&channel->lock); - return false; -} - -void lovrChannelClear(Channel* channel) { - mtx_lock(&channel->lock); - for (size_t i = channel->head; i < channel->messages.length; i++) { - lovrVariantDestroy(&channel->messages.data[i]); - } - channel->received = channel->sent; - arr_clear(&channel->messages); - channel->head = 0; - cnd_broadcast(&channel->cond); - mtx_unlock(&channel->lock); -} - -uint64_t lovrChannelGetCount(Channel* channel) { - mtx_lock(&channel->lock); - uint64_t length = channel->messages.length - channel->head; - mtx_unlock(&channel->lock); - return length; -} - -bool lovrChannelHasRead(Channel* channel, uint64_t id) { - mtx_lock(&channel->lock); - bool received = channel->received >= id; - mtx_unlock(&channel->lock); - return received; -} diff --git a/src/modules/thread/channel.h b/src/modules/thread/channel.h deleted file mode 100644 index bc5c8aec..00000000 --- a/src/modules/thread/channel.h +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -#pragma once - -struct Variant; - -typedef struct Channel Channel; -Channel* lovrChannelCreate(uint64_t hash); -void lovrChannelDestroy(void* ref); -bool lovrChannelPush(Channel* channel, struct Variant* variant, double timeout, uint64_t* id); -bool lovrChannelPop(Channel* channel, struct Variant* variant, double timeout); -bool lovrChannelPeek(Channel* channel, struct Variant* variant); -void lovrChannelClear(Channel* channel); -uint64_t lovrChannelGetCount(Channel* channel); -bool lovrChannelHasRead(Channel* channel, uint64_t id); diff --git a/src/modules/thread/thread.c b/src/modules/thread/thread.c index ee3fc218..5107b1a7 100644 --- a/src/modules/thread/thread.c +++ b/src/modules/thread/thread.c @@ -1,9 +1,35 @@ #include "thread/thread.h" -#include "thread/channel.h" +#include "data/blob.h" +#include "event/event.h" #include "util.h" +#include "lib/tinycthread/tinycthread.h" +#include #include #include +struct Thread { + uint32_t ref; + thrd_t handle; + mtx_t lock; + ThreadFunction* function; + Blob* body; + Variant arguments[MAX_THREAD_ARGUMENTS]; + uint32_t argumentCount; + char* error; + bool running; +}; + +struct Channel { + uint32_t ref; + mtx_t lock; + cnd_t cond; + arr_t(Variant) messages; + size_t head; + uint64_t sent; + uint64_t received; + uint64_t hash; +}; + static struct { bool initialized; mtx_t channelLock; @@ -47,12 +73,35 @@ Channel* lovrThreadGetChannel(const char* name) { return channel; } -Thread* lovrThreadCreate(int (*runner)(void*), Blob* body) { +// Thread + +static int threadFunction(void* data) { + Thread* thread = data; + lovrRetain(thread); + + char* error = thread->function(thread, thread->body, thread->arguments, thread->argumentCount); + + mtx_lock(&thread->lock); + thread->running = false; + if (error) { + thread->error = error; + lovrEventPush((Event) { + .type = EVENT_THREAD_ERROR, + .data.thread = { thread, thread->error } + }); + } + mtx_unlock(&thread->lock); + + lovrRelease(thread, lovrThreadDestroy); + return 0; +} + +Thread* lovrThreadCreate(ThreadFunction* function, Blob* body) { Thread* thread = calloc(1, sizeof(Thread)); lovrAssert(thread, "Out of memory"); thread->ref = 1; - thread->runner = runner; thread->body = body; + thread->function = function; mtx_init(&thread->lock, mtx_plain); lovrRetain(body); return thread; @@ -61,40 +110,178 @@ Thread* lovrThreadCreate(int (*runner)(void*), Blob* body) { void lovrThreadDestroy(void* ref) { Thread* thread = ref; mtx_destroy(&thread->lock); - thrd_detach(thread->handle); + if (thread->handle) thrd_detach(thread->handle); lovrRelease(thread->body, lovrBlobDestroy); free(thread->error); free(thread); } void lovrThreadStart(Thread* thread, Variant* arguments, uint32_t argumentCount) { - bool running = lovrThreadIsRunning(thread); - - if (running) { + mtx_lock(&thread->lock); + if (thread->running) { + mtx_unlock(&thread->lock); return; } free(thread->error); thread->error = NULL; + lovrAssert(argumentCount <= MAX_THREAD_ARGUMENTS, "Too many Thread arguments (max is %d)", MAX_THREAD_ARGUMENTS); - thread->argumentCount = argumentCount; memcpy(thread->arguments, arguments, argumentCount * sizeof(Variant)); - if (thrd_create(&thread->handle, thread->runner, thread) != thrd_success) { + thread->argumentCount = argumentCount; + + if (thrd_create(&thread->handle, threadFunction, thread) != thrd_success) { + mtx_unlock(&thread->lock); lovrThrow("Could not create thread...sorry"); } + + thread->running = true; + mtx_unlock(&thread->lock); } void lovrThreadWait(Thread* thread) { - thrd_join(thread->handle, NULL); + if (thread->handle) thrd_join(thread->handle, NULL); } bool lovrThreadIsRunning(Thread* thread) { - mtx_lock(&thread->lock); - bool running = thread->running; - mtx_unlock(&thread->lock); - return running; + return thread->running; } const char* lovrThreadGetError(Thread* thread) { return thread->error; } + +// Channel + +Channel* lovrChannelCreate(uint64_t hash) { + Channel* channel = calloc(1, sizeof(Channel)); + lovrAssert(channel, "Out of memory"); + channel->ref = 1; + arr_init(&channel->messages, arr_alloc); + mtx_init(&channel->lock, mtx_plain | mtx_timed); + cnd_init(&channel->cond); + channel->hash = hash; + return channel; +} + +void lovrChannelDestroy(void* ref) { + Channel* channel = ref; + lovrChannelClear(channel); + arr_free(&channel->messages); + mtx_destroy(&channel->lock); + cnd_destroy(&channel->cond); + free(channel); +} + +bool lovrChannelPush(Channel* channel, Variant* variant, double timeout, uint64_t* id) { + mtx_lock(&channel->lock); + if (channel->messages.length == 0) { + lovrRetain(channel); + } + arr_push(&channel->messages, *variant); + *id = ++channel->sent; + cnd_broadcast(&channel->cond); + + if (isnan(timeout) || timeout < 0) { + mtx_unlock(&channel->lock); + return false; + } + + while (channel->received < *id && timeout >= 0) { + if (isinf(timeout)) { + cnd_wait(&channel->cond, &channel->lock); + } else { + struct timespec start; + struct timespec until; + struct timespec stop; + timespec_get(&start, TIME_UTC); + double whole, fraction; + fraction = modf(timeout, &whole); + until.tv_sec = start.tv_sec + whole; + until.tv_nsec = start.tv_nsec + fraction * 1e9; + cnd_timedwait(&channel->cond, &channel->lock, &until); + timespec_get(&stop, TIME_UTC); + timeout -= (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec) / 1e9; + } + } + + bool read = channel->received >= *id; + mtx_unlock(&channel->lock); + return read; +} + +bool lovrChannelPop(Channel* channel, Variant* variant, double timeout) { + mtx_lock(&channel->lock); + + do { + if (channel->head < channel->messages.length) { + *variant = channel->messages.data[channel->head++]; + if (channel->head == channel->messages.length) { + channel->head = channel->messages.length = 0; + lovrRelease(channel, lovrChannelDestroy); + } + channel->received++; + cnd_broadcast(&channel->cond); + mtx_unlock(&channel->lock); + return true; + } else if (isnan(timeout) || timeout < 0) { + mtx_unlock(&channel->lock); + return false; + } + + if (isinf(timeout)) { + cnd_wait(&channel->cond, &channel->lock); + } else { + struct timespec start; + struct timespec until; + struct timespec stop; + timespec_get(&start, TIME_UTC); + double whole, fraction; + fraction = modf(timeout, &whole); + until.tv_sec = start.tv_sec + whole; + until.tv_nsec = start.tv_nsec + fraction * 1e9; + cnd_timedwait(&channel->cond, &channel->lock, &until); + timespec_get(&stop, TIME_UTC); + timeout -= (stop.tv_sec - start.tv_sec) + (stop.tv_nsec - start.tv_nsec) / (double) 1e9; + } + } while (1); +} + +bool lovrChannelPeek(Channel* channel, Variant* variant) { + mtx_lock(&channel->lock); + + if (channel->head < channel->messages.length) { + *variant = channel->messages.data[channel->head]; + mtx_unlock(&channel->lock); + return true; + } + + mtx_unlock(&channel->lock); + return false; +} + +void lovrChannelClear(Channel* channel) { + mtx_lock(&channel->lock); + for (size_t i = channel->head; i < channel->messages.length; i++) { + lovrVariantDestroy(&channel->messages.data[i]); + } + channel->received = channel->sent; + arr_clear(&channel->messages); + channel->head = 0; + cnd_broadcast(&channel->cond); + mtx_unlock(&channel->lock); +} + +uint64_t lovrChannelGetCount(Channel* channel) { + mtx_lock(&channel->lock); + uint64_t length = channel->messages.length - channel->head; + mtx_unlock(&channel->lock); + return length; +} + +bool lovrChannelHasRead(Channel* channel, uint64_t id) { + mtx_lock(&channel->lock); + bool received = channel->received >= id; + mtx_unlock(&channel->lock); + return received; +} diff --git a/src/modules/thread/thread.h b/src/modules/thread/thread.h index 426a291f..b39f4d19 100644 --- a/src/modules/thread/thread.h +++ b/src/modules/thread/thread.h @@ -1,6 +1,3 @@ -#include "data/blob.h" -#include "event/event.h" -#include "lib/tinycthread/tinycthread.h" #include #include @@ -11,28 +8,34 @@ #define MAX_THREAD_ARGUMENTS 4 -struct Channel; +struct Blob; +struct Variant; -typedef struct Thread { - uint32_t ref; - thrd_t handle; - mtx_t lock; - Blob* body; - Variant arguments[MAX_THREAD_ARGUMENTS]; - uint32_t argumentCount; - int (*runner)(void*); - char* error; - bool running; -} Thread; +typedef struct Thread Thread; +typedef struct Channel Channel; bool lovrThreadModuleInit(void); void lovrThreadModuleDestroy(void); struct Channel* lovrThreadGetChannel(const char* name); -void lovrThreadRemoveChannel(uint64_t hash); -Thread* lovrThreadCreate(int (*runner)(void*), Blob* body); +// Thread + +typedef char* ThreadFunction(Thread* thread, struct Blob* body, struct Variant* arguments, uint32_t argumentCount); + +Thread* lovrThreadCreate(ThreadFunction* function, struct Blob* body); void lovrThreadDestroy(void* ref); -void lovrThreadStart(Thread* thread, Variant* arguments, uint32_t argumentCount); +void lovrThreadStart(Thread* thread, struct Variant* arguments, uint32_t argumentCount); void lovrThreadWait(Thread* thread); -const char* lovrThreadGetError(Thread* thread); bool lovrThreadIsRunning(Thread* thread); +const char* lovrThreadGetError(Thread* thread); + +// Channel + +Channel* lovrChannelCreate(uint64_t hash); +void lovrChannelDestroy(void* ref); +bool lovrChannelPush(Channel* channel, struct Variant* variant, double timeout, uint64_t* id); +bool lovrChannelPop(Channel* channel, struct Variant* variant, double timeout); +bool lovrChannelPeek(Channel* channel, struct Variant* variant); +void lovrChannelClear(Channel* channel); +uint64_t lovrChannelGetCount(Channel* channel); +bool lovrChannelHasRead(Channel* channel, uint64_t id); diff --git a/src/util.h b/src/util.h index d8cecbff..22d0cdb2 100644 --- a/src/util.h +++ b/src/util.h @@ -67,7 +67,7 @@ typedef void* arr_allocator(void* data, size_t size); #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 +#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