From d76ea064705274c9eaf6c2520cb8669bf8dcfe76 Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Fri, 21 Feb 2025 21:16:15 +0300 Subject: [PATCH] poll workers for non-threaded support, emscripten main loop, remove twn_model.c --- CMakeLists.txt | 5 +- apps/templates/c/game.c | 9 + src/rendering/twn_gl_15_rendering.c | 38 ++--- src/rendering/twn_gl_any_rendering.c | 2 +- src/rendering/twn_model.c | 235 --------------------------- src/rendering/twn_models.c | 2 +- src/twn_filewatch.c | 2 +- src/twn_loop.c | 51 +++--- src/twn_main.c | 2 +- src/twn_textures.c | 2 +- src/twn_workers.c | 39 ++++- src/twn_workers_c.h | 7 +- 12 files changed, 105 insertions(+), 289 deletions(-) delete mode 100644 src/rendering/twn_model.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ce1be0e..b9ca869 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,8 @@ project(townengine LANGUAGES C) set(CMAKE_MESSAGE_LOG_LEVEL "WARNING") set(CMAKE_INSTALL_MESSAGE NEVER) +# TODO: test whether webgl 1 is good enough. + # SDL dependencies # for whatever reason Emscripten has SDL2 config, but not actual SDL2 port by default if(NOT EMSCRIPTEN) @@ -171,7 +173,8 @@ function(give_options_without_warnings target) set(LINK_FLAGS -Bsymbolic-functions - $<$:-sLEGACY_GL_EMULATION -sGL_FFP_ONLY -sGL_UNSAFE_OPTS=1> + $<$:-sLEGACY_GL_EMULATION -sGL_FFP_ONLY -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 + -sENVIRONMENT=web -sDEFAULT_TO_CXX=0> $<$:--preload-file ${TWN_OUT_DIR}/data@data -sALLOW_MEMORY_GROWTH> $<$>:-Wl,--as-needed> $<$:-Wl,--hash-style=gnu>) diff --git a/apps/templates/c/game.c b/apps/templates/c/game.c index 809a0fe..4b8ba4d 100644 --- a/apps/templates/c/game.c +++ b/apps/templates/c/game.c @@ -17,6 +17,15 @@ void game_tick(void) { struct state *state = ctx.udata; ++state->counter; + + m_sprite("nothing!", + (Rect) { + .x = 0, + .y = 0, + .w = 32, + .h = 32, + } + ); } diff --git a/src/rendering/twn_gl_15_rendering.c b/src/rendering/twn_gl_15_rendering.c index b443a84..42d7d0d 100644 --- a/src/rendering/twn_gl_15_rendering.c +++ b/src/rendering/twn_gl_15_rendering.c @@ -5,7 +5,7 @@ #include "twn_types.h" #include "twn_deferred_commands.h" -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ #define GL_GLEXT_PROTOTYPES #include #include @@ -41,7 +41,7 @@ static void APIENTRY opengl_log(GLenum source, bool render_init(void) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ if (gladLoadGLLoader(&SDL_GL_GetProcAddress) == 0) { CRY("Init", "GLAD failed"); return false; @@ -50,7 +50,7 @@ bool render_init(void) { log_info("OpenGL context: %s\n", glGetString(GL_VERSION)); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); glHint(GL_FOG_HINT, GL_FASTEST); @@ -98,7 +98,7 @@ static void finally_use_space_pipeline(void) { depth_range_high = 1.0; depth_range_low = 0.0; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ static GLuint list = 0; if (!list) { list = glGenLists(1); @@ -109,7 +109,7 @@ static void finally_use_space_pipeline(void) { glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glShadeModel(GL_FLAT); - #ifndef EMSCRIPTEN + #ifndef __EMSCRIPTEN__ if (GLAD_GL_ARB_depth_clamp) glDisable(GL_DEPTH_CLAMP); #endif @@ -125,7 +125,7 @@ static void finally_use_space_pipeline(void) { /* solid white, no modulation */ glColor4ub(255, 255, 255, 255); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ } glEndList(); glCallList(list); @@ -152,7 +152,7 @@ static void finally_use_2d_pipeline(void) { if (pipeline_last_used == PIPELINE_2D) return; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ static GLuint list = 0; if (!list) { list = glGenLists(1); @@ -161,7 +161,7 @@ static void finally_use_2d_pipeline(void) { glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); glShadeModel(GL_FLAT); - #ifndef EMSCRIPTEN + #ifndef __EMSCRIPTEN__ /* removes near/far plane comparison and discard */ if (GLAD_GL_ARB_depth_clamp) glEnable(GL_DEPTH_CLAMP); @@ -171,7 +171,7 @@ static void finally_use_2d_pipeline(void) { glActiveTexture(GL_TEXTURE0); glDisable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ } glEndList(); } glCallList(list); @@ -201,7 +201,7 @@ static void finally_use_texture_mode(TextureMode mode) { if (texture_mode_last_used == mode) return; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ static GLuint lists = 0; if (!lists) { lists = glGenLists(3); @@ -214,7 +214,7 @@ static void finally_use_texture_mode(TextureMode mode) { glDepthFunc(GL_LESS); glDepthMask(GL_FALSE); glDisable(GL_ALPHA_TEST); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ } glEndList(); /* seethrough */ @@ -225,7 +225,7 @@ static void finally_use_texture_mode(TextureMode mode) { glDepthMask(GL_TRUE); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_EQUAL, 1.0f); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ } glEndList(); /* opaque */ @@ -235,7 +235,7 @@ static void finally_use_texture_mode(TextureMode mode) { glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); glDisable(GL_ALPHA_TEST); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ } glEndList(); } @@ -257,7 +257,7 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) { SDL_assert(bytes != 0); glBindBuffer(GL_ARRAY_BUFFER, buffer); glBufferData(GL_ARRAY_BUFFER, bytes, NULL, GL_STREAM_DRAW); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ void *mapping = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY); #else void *mapping = SDL_malloc(bytes); @@ -273,7 +273,7 @@ VertexBufferBuilder build_vertex_buffer(VertexBuffer buffer, size_t bytes) { void finish_vertex_builder(VertexBufferBuilder *builder) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ if (!glUnmapBuffer(GL_ARRAY_BUFFER)) CRY("finish_vertex_builder", "Error unmapping a vertex array buffer"); #else @@ -372,7 +372,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) { /* TODO: figure out which coordinates to use to not have issues with far z */ /* TODO: recalculate the list if far z requirement changes */ -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ static GLuint list = 0; if (!list) { list = glGenLists(1); @@ -384,7 +384,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) { glDisable(GL_ALPHA_TEST); glDepthMask(GL_FALSE); - #ifndef EMSCRIPTEN + #ifndef __EMSCRIPTEN__ /* removes near/far plane comparison and discard */ if (GLAD_GL_ARB_depth_clamp) glEnable(GL_DEPTH_CLAMP); @@ -452,7 +452,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) { glVertex3f(-50.f, 50.f, -50.f); } glEnd(); - #ifndef EMSCRIPTEN + #ifndef __EMSCRIPTEN__ if (GLAD_GL_ARB_depth_clamp) glDisable(GL_DEPTH_CLAMP); #endif @@ -460,7 +460,7 @@ void finally_render_skybox(DeferredCommandDrawSkybox command) { glDepthMask(GL_TRUE); glDisable(GL_TEXTURE_CUBE_MAP); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ } glEndList(); glCallList(list); #endif diff --git a/src/rendering/twn_gl_any_rendering.c b/src/rendering/twn_gl_any_rendering.c index ec6e922..21c5ce7 100644 --- a/src/rendering/twn_gl_any_rendering.c +++ b/src/rendering/twn_gl_any_rendering.c @@ -4,7 +4,7 @@ #include -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ #define GL_GLEXT_PROTOTYPES #include #include diff --git a/src/rendering/twn_model.c b/src/rendering/twn_model.c deleted file mode 100644 index 1f27a0c..0000000 --- a/src/rendering/twn_model.c +++ /dev/null @@ -1,235 +0,0 @@ -#include "twn_draw_c.h" -#include "twn_draw.h" -#include "twn_workers_c.h" -#include "twn_textures_c.h" - -#define FAST_OBJ_IMPLEMENTATION -#define FAST_OBJ_REALLOC SDL_realloc -#define FAST_OBJ_FREE SDL_free -#include -#include -#include -#include - - -static struct ModelCacheItem { - char *key; - struct ModelCacheItemValue { - fastObjMesh *mesh; - } value; -} *model_cache; - -/* TODO: store index to model cache instead */ -static struct ModelDrawCommand { - char *model; - Vec3 position; - Vec3 rotation; - Vec3 scale; -} *model_draw_commands; - -/* deferred queue of model files to load from worker threads */ -static SDL_mutex *model_load_mutex; -static char const **model_load_queue; -static size_t model_load_queued; -static bool model_load_initialized; - -/* use streaming via callbacks to reduce memory congestion */ -static void model_load_callback_close(void *handle, void *udata) { - (void)udata; - ((SDL_RWops *)handle)->close(handle); -} - -static void *model_load_callback_open(const char *path, void *udata) { - (void)udata; - return PHYSFSRWOPS_openRead(path); -} - -static size_t model_load_callback_read(void *handle, void *dst, size_t bytes, void *udata) { - (void)udata; - return ((SDL_RWops *)handle)->read(handle, dst, 1, bytes); -} - -static unsigned long model_load_callback_size(void *handle, void *udata) { - (void)udata; - return ((SDL_RWops *)handle)->size(handle); -} - - -/* TODO: is there a way to do this nicely while locking main thread? */ -/* sleeping over atomic counter might be good enough i guess */ -/* it's safe to access everything without lock after this returns true and no public api is possible to call */ -static bool model_load_workers_finished(void) { - bool result; - SDL_LockMutex(model_load_mutex); - result = model_load_queued == 0; - SDL_UnlockMutex(model_load_mutex); - return result; -} - - -/* entry point for workers, polled every time a job semaphore is posted */ -/* returns false if there was nothing to do */ -bool model_load_workers_thread(void) { - /* attempt to grab something to work on */ - char const *load_request = NULL; - SDL_LockMutex(model_load_mutex); - if (arrlenu(model_load_queue) != 0) - load_request = arrpop(model_load_queue); - SDL_UnlockMutex(model_load_mutex); - /* nothing to do, bail */ - if (!load_request) - return false; - - fastObjCallbacks const callbacks = { - .file_close = model_load_callback_close, - .file_open = model_load_callback_open, - .file_read = model_load_callback_read, - .file_size = model_load_callback_size - }; - - fastObjMesh *const mesh = fast_obj_read_with_callbacks(load_request, &callbacks, NULL); - - SDL_LockMutex(model_load_mutex); - struct ModelCacheItem *item = shgetp(model_cache, load_request); - item->value.mesh = mesh; - model_load_queued--; - SDL_UnlockMutex(model_load_mutex); - - return true; -} - - -void draw_model(const char *model, - Vec3 position, - Vec3 rotation, - Vec3 scale) -{ - if (!model_load_initialized) { - model_load_mutex = SDL_CreateMutex(); - model_load_initialized = true; - } - - struct ModelCacheItem const *item; - - /* TODO: make it lockless */ - /* if model is missing, queue it up for loading */ - SDL_LockMutex(model_load_mutex); - if (!(item = shgetp_null(model_cache, model))) { - model = SDL_strdup(model); - shput(model_cache, model, (struct ModelCacheItemValue){0}); - arrpush(model_load_queue, model); - model_load_queued++; - SDL_SemPost(workers_job_semaphore); - } else - model = item->key; - SDL_UnlockMutex(model_load_mutex); - - struct ModelDrawCommand const command = { - .model = (char *)model, - .position = position, - .rotation = rotation, - .scale = scale - }; - arrpush(model_draw_commands, command); -} - - -void finally_draw_models(void) { - while (!model_load_workers_finished()) - SDL_Delay(1); - - /* TODO: have special path for them, preserving the buffers and potentially using instanced draw */ - for (int i = 0; i < arrlen(model_draw_commands); ++i) { - struct ModelDrawCommand const *const command = &model_draw_commands[i]; - fastObjMesh const *const mesh = model_cache[shgeti(model_cache, command->model)].value.mesh; - SDL_assert(mesh); - for (unsigned int g = 0; g < mesh->group_count; ++g) { - fastObjGroup const *const group = &mesh->groups[g]; - unsigned int idx = 0; - for (unsigned int f = 0; f < group->face_count; ++f) { - unsigned int const vertices = mesh->face_vertices[group->face_offset + f]; - // fastObjTexture const *const texture = &mesh->textures[group->face_offset + f]; - // log_info("material: %s", material->name); - /* TODO: support arbitrary fans */ - unsigned int const material_index = mesh->face_materials[group->index_offset + f]; - fastObjMaterial const *const material = mesh->materials ? &mesh->materials[material_index] : NULL; - if (vertices == 4) { - fastObjIndex const i0 = mesh->indices[group->index_offset + idx + 0]; - fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1]; - fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2]; - fastObjIndex const i3 = mesh->indices[group->index_offset + idx + 3]; - draw_quad( - material ? mesh->textures[material->map_Kd].name : NULL, - (Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i3.p + 0] * command->scale.x, mesh->positions[3 * i3.p + 1] * command->scale.y, mesh->positions[3 * i3.p + 2] * command->scale.z }, - (Rect) { .w = 64, .h = 64 }, - (Color) { 255, 255, 255, 255 } - ); - } else if (vertices == 3) { - fastObjIndex const i0 = mesh->indices[group->index_offset + idx + 0]; - fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1]; - fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2]; - draw_triangle( - material ? mesh->textures[material->map_Kd].name : NULL, - (Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z }, - (Vec2) {0,0}, - (Vec2) {0,0}, - (Vec2) {0,0}, - (Color){255, 255, 255, 255}, - (Color){255, 255, 255, 255}, - (Color){255, 255, 255, 255} - ); - } else { - fastObjIndex const i0 = mesh->indices[group->index_offset + idx]; - for (unsigned int z = 0; z < vertices - 2; ++z) { - fastObjIndex const i1 = mesh->indices[group->index_offset + idx + 1 + z]; - fastObjIndex const i2 = mesh->indices[group->index_offset + idx + 2 + z]; - draw_triangle( - material ? mesh->textures[material->map_Kd].name : NULL, - (Vec3) { mesh->positions[3 * i0.p + 0] * command->scale.x, mesh->positions[3 * i0.p + 1] * command->scale.y, mesh->positions[3 * i0.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i1.p + 0] * command->scale.x, mesh->positions[3 * i1.p + 1] * command->scale.y, mesh->positions[3 * i1.p + 2] * command->scale.z }, - (Vec3) { mesh->positions[3 * i2.p + 0] * command->scale.x, mesh->positions[3 * i2.p + 1] * command->scale.y, mesh->positions[3 * i2.p + 2] * command->scale.z }, - (Vec2) {0,0}, - (Vec2) {0,0}, - (Vec2) {0,0}, - (Color){255, 255, 255, 255}, - (Color){255, 255, 255, 255}, - (Color){255, 255, 255, 255} - ); - } - } - idx += vertices; - } - } - } - - arrsetlen(model_draw_commands, 0); -} - - -/* drop model caches */ -void free_model_cache(void) { - while (!model_load_workers_finished()) - SDL_Delay(1); - - for (size_t i = 0; i < shlenu(model_cache); ++i) { - fast_obj_destroy(model_cache[i].value.mesh); - SDL_free(model_cache[i].key); - } - - shfree(model_cache); -} - - -void model_state_deinit(void) { - if (!model_load_initialized) - return; - free_model_cache(); - arrfree(model_load_queue); - SDL_DestroyMutex(model_load_mutex); - model_load_initialized = false; -} diff --git a/src/rendering/twn_models.c b/src/rendering/twn_models.c index 681f6c8..2fb886b 100644 --- a/src/rendering/twn_models.c +++ b/src/rendering/twn_models.c @@ -162,7 +162,7 @@ void draw_model(const char *model, }; arrpush(model_load_queue, request); SDL_UnlockMutex(model_load_mutex); - SDL_SemPost(workers_job_semaphore); + workers_add_job(); } else modelcopy = item->key; diff --git a/src/twn_filewatch.c b/src/twn_filewatch.c index 2d135e9..182a162 100644 --- a/src/twn_filewatch.c +++ b/src/twn_filewatch.c @@ -2,7 +2,7 @@ #include "twn_util.h" #include "twn_engine_context_c.h" -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #define DMON_IMPL #include #include diff --git a/src/twn_loop.c b/src/twn_loop.c index 5e77d59..b1ccbf5 100644 --- a/src/twn_loop.c +++ b/src/twn_loop.c @@ -16,7 +16,9 @@ #include #include - +#ifdef __EMSCRIPTEN__ +#include +#endif #define TICKS_PER_SECOND_DEFAULT 60 #define PACKAGE_EXTENSION "btw" @@ -24,7 +26,7 @@ static void pack_contents_modified(char const *path, enum FilewatchAction action); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ static SDL_sem *opengl_load_semaphore; #endif @@ -105,14 +107,20 @@ static void update_viewport(void) { } + +static void garbage_collect(void) { + file_read_garbage_collect(); +} + + static void main_loop(void) { - /* - if (!ctx.is_running) { - end(ctx); - clean_up(ctx); +#ifdef __EMSCRIPTEN__ + if (!ctx.is_running) emscripten_cancel_main_loop(); - } - */ +#endif + + /* dispatch all filewatch driven events, such as game object and asset pack reload */ + filewatch_poll(); /* frame timer */ int64_t current_frame_time = SDL_GetPerformanceCounter(); @@ -228,6 +236,8 @@ static void main_loop(void) { ctx.game.initialization_needed = false; } + workers_poll(); + /* TODO: in some cases machine might want to assume frames will be fed as much as possible */ /* which for now is broken as glBufferData with NULL is used all over right after render */ if (frames != 0) @@ -236,6 +246,8 @@ static void main_loop(void) { /* don't waste clock cycles on useless work */ /* TODO: make it adjustable from config */ SDL_Delay((uint32_t)(ctx.desired_frametime - ctx.frame_accumulator) / 1250000); + + garbage_collect(); } @@ -425,7 +437,7 @@ static bool initialize(void) { toml_datum_t datum_debug = toml_bool_in(game, "debug"); ctx.game.debug = datum_debug.ok ? datum_debug.u.b : true; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5); @@ -445,6 +457,8 @@ static bool initialize(void) { #else SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); #endif toml_datum_t datum_title = toml_string_in(about, "title"); @@ -630,7 +644,7 @@ static bool initialize(void) { profile_end("game object load"); /* delayed as further as possible so that more work is done before we have to wait */ -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ SDL_SemWait(opengl_load_semaphore); SDL_DestroySemaphore(opengl_load_semaphore); profile_end("opengl loading"); @@ -756,11 +770,6 @@ static bool try_mounting_root_pack(char *path) { } -static void garbage_collect(void) { - file_read_garbage_collect(); -} - - int enter_loop(int argc, char **argv) { profile_start("startup"); @@ -771,7 +780,7 @@ int enter_loop(int argc, char **argv) { } profile_end("SDL initialization"); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ profile_start("opengl loading"); opengl_load_semaphore = SDL_CreateSemaphore(0); SDL_Thread *opengl_load_thread = SDL_CreateThread(opengl_load_thread_fn, "opengl loader", opengl_load_semaphore); @@ -871,12 +880,12 @@ int enter_loop(int argc, char **argv) { profile_end("startup"); - while (ctx.is_running) { - /* dispatch all filewatch driven events, such as game object and asset pack reload */ - filewatch_poll(); +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(main_loop, ctx.desired_frametime, true); +#else + while (ctx.is_running) main_loop(); - garbage_collect(); - } +#endif if (ctx.game.debug) profile_list_stats(); diff --git a/src/twn_main.c b/src/twn_main.c index 652749d..88b40de 100644 --- a/src/twn_main.c +++ b/src/twn_main.c @@ -1,6 +1,6 @@ #include "twn_loop_c.h" -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #define SDL_MAIN_HANDLED #endif #include diff --git a/src/twn_textures.c b/src/twn_textures.c index b7db50d..b47e1ac 100644 --- a/src/twn_textures.c +++ b/src/twn_textures.c @@ -466,7 +466,7 @@ static TextureKey textures_load(TextureCache *cache, const char *path) { SDL_UnlockMutex(textures_load_mutex); /* signal work to do */ - SDL_SemPost(workers_job_semaphore); + workers_add_job(); cache->is_dirty = true; diff --git a/src/twn_workers.c b/src/twn_workers.c index 6c4afef..3a0c010 100644 --- a/src/twn_workers.c +++ b/src/twn_workers.c @@ -2,12 +2,19 @@ #include "twn_workers_c.h" #include "rendering/twn_draw_c.h" +#ifndef __EMSCRIPTEN__ SDL_sem *workers_job_semaphore; -static size_t workers_pool_size; static SDL_mutex *workers_mutex; -static bool workers_should_exit; static SDL_sem *workers_exit_semaphore; /* should come to count of `workers_pool_size` */ +static bool workers_should_exit; +#else +/* accomulate to late poll from main thread itself */ +static uint32_t workers_job_count; +#endif + +static size_t workers_pool_size; + /* logic is such that when job is posted, worker threads attempt to grab it from any possible entry point */ /* if it did something, which is signaled by `true` return, go back to waiting on semaphore, so that it's decremented properly */ @@ -15,6 +22,7 @@ static int worker_thread(void *udata) { (void)udata; while (true) { +#ifndef __EMSCRIPTEN__ /* check whether loop should end */ SDL_LockMutex(workers_mutex); if (workers_should_exit) { @@ -26,6 +34,11 @@ static int worker_thread(void *udata) { /* wait and occasionally go back to check whether it all should end */ if (SDL_SemWaitTimeout(workers_job_semaphore, 100) == SDL_MUTEX_TIMEDOUT) continue; +#else + if (workers_job_count <= 0) + break; + workers_job_count--; +#endif /* process models, which will trigger texture loads */ if (models_load_workers_thread()) @@ -35,8 +48,10 @@ static int worker_thread(void *udata) { continue; } +#ifndef __EMSCRIPTEN__ /* let the main thread collect it */ SDL_SemPost(workers_exit_semaphore); +#endif return 0; } @@ -50,7 +65,7 @@ bool workers_init(size_t worker_count) { if (worker_count > MAX_WORKERS) worker_count = MAX_WORKERS; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ /* spawn a bunch of detached threads without references to them */ for (size_t i = 0; i < worker_count; ++i) { SDL_Thread *thread = SDL_CreateThread(worker_thread, "worker", NULL); @@ -68,7 +83,7 @@ bool workers_init(size_t worker_count) { void workers_deinit(void) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ SDL_LockMutex(workers_mutex); workers_should_exit = true; SDL_UnlockMutex(workers_mutex); @@ -83,3 +98,19 @@ void workers_deinit(void) { #endif workers_pool_size = 0; } + + +void workers_add_job(void) { +#ifndef __EMSCRIPTEN__ + SDL_SemPost(workers_job_semaphore); +#else + workers_job_count++; +#endif +} + +void workers_poll(void) { +#ifdef __EMSCRIPTEN__ + worker_thread(NULL); +#else +#endif +} diff --git a/src/twn_workers_c.h b/src/twn_workers_c.h index 0c9016a..1108fd9 100644 --- a/src/twn_workers_c.h +++ b/src/twn_workers_c.h @@ -7,11 +7,10 @@ #define MAX_WORKERS 9 -/* workers are waiting on this, increment this value when some work needs to be done */ -/* for now every possible job path is hardcoded in twn_workers.c itself */ -extern SDL_sem *workers_job_semaphore; - bool workers_init(size_t worker_count); void workers_deinit(void); +void workers_add_job(void); +/* for targets lacking threading support main thread could do the work */ +void workers_poll(void); #endif