mirror of https://github.com/bjornbytes/lovr.git
Merge branch 'master' into dev
This commit is contained in:
commit
e12563ad25
|
@ -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
|
||||
|
|
|
@ -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 💜
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 <lualib.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include "api.h"
|
||||
#include "thread/channel.h"
|
||||
#include "thread/thread.h"
|
||||
#include "event/event.h"
|
||||
#include "util.h"
|
||||
#include <math.h>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <stdio.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -14,10 +14,6 @@ bool os_init() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void os_destroy() {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
const char* os_get_name() {
|
||||
return "Linux";
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -62,10 +62,6 @@ bool os_init() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void os_destroy() {
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
const char* os_get_name() {
|
||||
return "Windows";
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
#include "thread/channel.h"
|
||||
#include "event/event.h"
|
||||
#include "util.h"
|
||||
#include "lib/tinycthread/tinycthread.h"
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <math.h>
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
|
@ -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 <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
#include "data/blob.h"
|
||||
#include "event/event.h"
|
||||
#include "lib/tinycthread/tinycthread.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue