diff options
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | abletonlink-1.0.1-1.rockspec | 1 | ||||
| -rw-r--r-- | abletonlink.c | 236 | ||||
| -rw-r--r-- | examples/linkhut.lua | 13 | ||||
| -rw-r--r-- | tinycthread.c | 588 | ||||
| -rw-r--r-- | tinycthread.h | 437 |
6 files changed, 1269 insertions, 13 deletions
@@ -31,7 +31,12 @@ The module table has three members: Destroys this instance. Calling any other method on a destroyed instance causes an error. This is automatically called in __gc. -- `link:set_*_callback` are not currently implemented. +- `link:pump_events()`: + If callbacks are installed, this needs to be called regularily to dispatch events. + It will return `true` if the internal event queue overran. +- `link:set_num_peers_callback(cb)`: cb is called with number of peers (int). +- `link:set_tempo_callback(cb)`: cb is called with new tempo (int). +- `link:set_start_stop_callback(cb)`: cb is called with playing state (bool). `abl_link_session_state` instances have the following methods: diff --git a/abletonlink-1.0.1-1.rockspec b/abletonlink-1.0.1-1.rockspec index 60aa423..0672546 100644 --- a/abletonlink-1.0.1-1.rockspec +++ b/abletonlink-1.0.1-1.rockspec @@ -26,6 +26,7 @@ build = { abletonlink = { sources = { 'abletonlink.c', + 'tinycthreads.c', 'link/extensions/abl_link/src/abl_link.cpp', }, incdirs = { diff --git a/abletonlink.c b/abletonlink.c index 1e603fa..c4a280e 100644 --- a/abletonlink.c +++ b/abletonlink.c @@ -1,6 +1,7 @@ #include <lua.h> #include <lauxlib.h> #include "link/extensions/abl_link/include/abl_link.h" +#include "tinycthread.h" #if LUA_VERSION_NUM > 501 #define luax_len(L, i) (int) lua_rawlen(L, i) @@ -11,12 +12,88 @@ #define LUA_RIDX_MAINTHREAD 1 #endif +typedef struct { + enum { + EVENT_TYPE_NUM_PEERS, + EVENT_TYPE_TEMPO, + EVENT_TYPE_START_STOP, + } type; + union { + uint64_t num_peers; + double tempo; + bool start_stop; + }; +} event_t; + +#define NUM_EVENTS 64 + +typedef struct { + event_t buffer[NUM_EVENTS]; + event_t *head; + event_t *tail; + event_t *end; + bool full; +} event_queue_t; + +void eq_init(event_queue_t *queue) { + queue->head = queue->buffer; + queue->tail = queue->buffer; + queue->end = queue->buffer + NUM_EVENTS; +} + +static void eq_advance(event_queue_t *queue, event_t **ptr) { + (*ptr)++; + if (*ptr == queue->end) { + *ptr = queue->buffer; + } +} + +void eq_push(event_queue_t *queue, event_t evt) { + if (queue->full) return; + + *queue->head = evt; + + eq_advance(queue, &queue->head); + + if (queue->head == queue->tail) { + queue->full = true; + } +} + +bool eq_pop(event_queue_t *queue, event_t *evt) { + if (!queue->full && queue->head == queue->tail) { + return false; + } + + *evt = *(queue->tail); + eq_advance(queue, &queue->tail); + + return true; +} + +typedef struct { + abl_link link; + + int cb_num_peers; + int cb_tempo; + int cb_start_stop; + + mtx_t mutex; + event_queue_t queue; +} link_state_t; + static int l_create_link(lua_State* L) { double bpm = luaL_checknumber(L, 1); - abl_link* link = (abl_link*)lua_newuserdata(L, sizeof(abl_link)); + link_state_t* state = (link_state_t*)lua_newuserdata(L, sizeof(link_state_t)); - *link = abl_link_create(bpm); + state->link = abl_link_create(bpm); + state->cb_num_peers = LUA_NOREF; + state->cb_tempo = LUA_NOREF; + state->cb_start_stop = LUA_NOREF; + + mtx_init(&state->mutex, mtx_plain); + eq_init(&state->queue); luaL_getmetatable(L, "abletonlink.link"); lua_setmetatable(L, -2); @@ -35,11 +112,16 @@ static int l_create_session_state(lua_State* L) { return 1; } +static link_state_t *checklinkstate(lua_State *L) { + link_state_t* state = (link_state_t*)luaL_checkudata(L, 1, "abletonlink.link"); + luaL_argcheck(L, state != NULL, 1, "`link' expected"); + luaL_argcheck(L, state->link.impl != NULL, 1, "`link' is already destroyed"); + return state; +} + static abl_link checklink(lua_State *L) { - abl_link* link = (abl_link*)luaL_checkudata(L, 1, "abletonlink.link"); - luaL_argcheck(L, link != NULL, 1, "`link' expected"); - luaL_argcheck(L, link->impl != NULL, 1, "`link' is already destroyed"); - return *link; + link_state_t* state = checklinkstate(L); + return state->link; } static abl_link_session_state checksessionstate(lua_State *L, int ud) { @@ -50,12 +132,22 @@ static abl_link_session_state checksessionstate(lua_State *L, int ud) { } static int l_destroy_link(lua_State* L) { - abl_link* link = (abl_link*)luaL_checkudata(L, 1, "abletonlink.link"); - luaL_argcheck(L, link != NULL, 1, "`link' expected"); - if (link->impl) { - abl_link_destroy(*link); - link->impl = NULL; + link_state_t* state = checklinkstate(L); + + if (state->link.impl) { + abl_link_destroy(state->link); + + luaL_unref(L, LUA_REGISTRYINDEX, state->cb_num_peers); + luaL_unref(L, LUA_REGISTRYINDEX, state->cb_tempo); + luaL_unref(L, LUA_REGISTRYINDEX, state->cb_start_stop); + state->cb_num_peers = LUA_NOREF; + state->cb_tempo = LUA_NOREF; + state->cb_start_stop = LUA_NOREF; + + mtx_destroy(&state->mutex); + state->link.impl = NULL; } + return 0; } @@ -125,10 +217,126 @@ static int l_capture_app_session_state(lua_State* L) { static int l_commit_app_session_state(lua_State* L) { abl_link link = checklink(L); abl_link_session_state session_state = checksessionstate(L, 2); - abl_link_commit_app_session_state(link, session_state); + abl_link_commit_app_session_state(link, session_state); + return 0; +} + +static void cb_num_peers(uint64_t num_peers, void *context) { + link_state_t* state = (link_state_t *)context; + event_t evt; + evt.type = EVENT_TYPE_NUM_PEERS; + evt.num_peers = num_peers; + eq_push(&state->queue, evt); +} +static int l_set_num_peers_callback(lua_State* L) { + link_state_t* state = checklinkstate(L); + + luaL_unref(L, LUA_REGISTRYINDEX, state->cb_num_peers); + + if (lua_isnil(L, 2)) { + state->cb_num_peers = LUA_NOREF; + abl_link_set_num_peers_callback(state->link, NULL, NULL); + return 0; + } + + luaL_checktype(L, 2, LUA_TFUNCTION); + lua_pushvalue(L, 2); + state->cb_num_peers = luaL_ref(L, LUA_REGISTRYINDEX); + abl_link_set_num_peers_callback(state->link, cb_num_peers, state); return 0; } +static void cb_tempo(double tempo, void *context) { + link_state_t* state = (link_state_t *)context; + event_t evt; + evt.type = EVENT_TYPE_TEMPO; + evt.tempo = tempo; + eq_push(&state->queue, evt); +} +static int l_set_tempo_callback(lua_State* L) { + link_state_t* state = checklinkstate(L); + + luaL_unref(L, LUA_REGISTRYINDEX, state->cb_tempo); + + if (lua_isnil(L, 2)) { + state->cb_tempo = LUA_NOREF; + abl_link_set_tempo_callback(state->link, NULL, NULL); + return 0; + } + + luaL_checktype(L, 2, LUA_TFUNCTION); + lua_pushvalue(L, 2); + state->cb_tempo = luaL_ref(L, LUA_REGISTRYINDEX); + abl_link_set_tempo_callback(state->link, cb_tempo, state); + return 0; +} + +static void cb_start_stop(bool start_stop, void *context) { + link_state_t* state = (link_state_t *)context; + event_t evt; + evt.type = EVENT_TYPE_START_STOP; + evt.start_stop = start_stop; + eq_push(&state->queue, evt); +} +static int l_set_start_stop_callback(lua_State* L) { + link_state_t* state = checklinkstate(L); + + luaL_unref(L, LUA_REGISTRYINDEX, state->cb_start_stop); + + if (lua_isnil(L, 2)) { + state->cb_start_stop = LUA_NOREF; + abl_link_set_start_stop_callback(state->link, NULL, NULL); + return 0; + } + + luaL_checktype(L, 2, LUA_TFUNCTION); + lua_pushvalue(L, 2); + state->cb_start_stop = luaL_ref(L, LUA_REGISTRYINDEX); + abl_link_set_start_stop_callback(state->link, cb_start_stop, state); + return 0; +} + +static int l_pump_events(lua_State* L) { + link_state_t *state = checklinkstate(L); + + bool was_full = state->queue.full; + state->queue.full = false; + + event_t evt; + while (eq_pop(&state->queue, &evt)) { + mtx_lock(&state->mutex); + + switch (evt.type) { + case EVENT_TYPE_NUM_PEERS: + if (state->cb_num_peers == LUA_NOREF) continue; + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->cb_num_peers); + lua_pushinteger(L, evt.num_peers); + break; + + case EVENT_TYPE_TEMPO: + if (state->cb_tempo == LUA_NOREF) continue; + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->cb_tempo); + lua_pushnumber(L, evt.tempo); + break; + + case EVENT_TYPE_START_STOP: + if (state->cb_start_stop == LUA_NOREF) continue; + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->cb_start_stop); + lua_pushinteger(L, evt.start_stop); + break; + } + + mtx_unlock(&state->mutex); + lua_call(L, 1, 0); + } + + lua_pushboolean(L, was_full); + return 1; +} + static const luaL_Reg abl_link_r[] = { { "destroy", l_destroy_link }, { "is_enabled", l_is_enabled}, @@ -141,6 +349,10 @@ static const luaL_Reg abl_link_r[] = { { "commit_audio_session_state", l_commit_audio_session_state }, { "capture_app_session_state", l_capture_app_session_state }, { "commit_app_session_state", l_commit_app_session_state }, + { "set_num_peers_callback", l_set_num_peers_callback }, + { "set_tempo_callback", l_set_tempo_callback }, + { "set_start_stop_callback", l_set_start_stop_callback }, + { "pump_events", l_pump_events }, { "__gc", l_destroy_link }, { NULL, NULL }, }; diff --git a/examples/linkhut.lua b/examples/linkhut.lua index 7490823..de3ba9e 100644 --- a/examples/linkhut.lua +++ b/examples/linkhut.lua @@ -102,6 +102,18 @@ session_state = al.create_session_state() running = true quantum = 4 +link:set_num_peers_callback(function (np) + print("\nevent:", "num peers", np) +end) + +link:set_tempo_callback(function (t) + print("\nevent:", "tempo", t) +end) + +link:set_start_stop_callback(function (p) + print("\nevent:", "start_stop", p) +end) + print_help() print_state_header() set_buffered_input(false) @@ -113,5 +125,6 @@ while running do else print_state() end + link:pump_events() end set_buffered_input(true) diff --git a/tinycthread.c b/tinycthread.c new file mode 100644 index 0000000..ced7cf3 --- /dev/null +++ b/tinycthread.c @@ -0,0 +1,588 @@ +/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "tinycthread.h" +#include <stdlib.h> + +/* Platform specific includes */ +#if defined(_TTHREAD_POSIX_) + #include <signal.h> + #include <sched.h> + #include <unistd.h> + #include <sys/time.h> + #include <errno.h> +#elif defined(_TTHREAD_WIN32_) + #include <process.h> + #include <sys/timeb.h> +#endif + +/* Standard, good-to-have defines */ +#ifndef NULL + #define NULL (void*)0 +#endif +#ifndef TRUE + #define TRUE 1 +#endif +#ifndef FALSE + #define FALSE 0 +#endif + +int mtx_init(mtx_t *mtx, int type) +{ +#if defined(_TTHREAD_WIN32_) + mtx->mAlreadyLocked = FALSE; + mtx->mRecursive = type & mtx_recursive; + InitializeCriticalSection(&mtx->mHandle); + return thrd_success; +#else + int ret; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + if (type & mtx_recursive) + { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } + ret = pthread_mutex_init(mtx, &attr); + pthread_mutexattr_destroy(&attr); + return ret == 0 ? thrd_success : thrd_error; +#endif +} + +void mtx_destroy(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mtx->mHandle); +#else + pthread_mutex_destroy(mtx); +#endif +} + +int mtx_lock(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mtx->mHandle); + if (!mtx->mRecursive) + { + while(mtx->mAlreadyLocked) Sleep(1000); /* Simulate deadlock... */ + mtx->mAlreadyLocked = TRUE; + } + return thrd_success; +#else + return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error; +#endif +} + +int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) +{ + /* FIXME! */ + (void)mtx; + (void)ts; + return thrd_error; +} + +int mtx_trylock(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + int ret = TryEnterCriticalSection(&mtx->mHandle) ? thrd_success : thrd_busy; + if ((!mtx->mRecursive) && (ret == thrd_success) && mtx->mAlreadyLocked) + { + LeaveCriticalSection(&mtx->mHandle); + ret = thrd_busy; + } + return ret; +#else + return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; +#endif +} + +int mtx_unlock(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + mtx->mAlreadyLocked = FALSE; + LeaveCriticalSection(&mtx->mHandle); + return thrd_success; +#else + return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;; +#endif +} + +#if defined(_TTHREAD_WIN32_) +#define _CONDITION_EVENT_ONE 0 +#define _CONDITION_EVENT_ALL 1 +#endif + +int cnd_init(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + cond->mWaitersCount = 0; + + /* Init critical section */ + InitializeCriticalSection(&cond->mWaitersCountLock); + + /* Init events */ + cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL) + { + cond->mEvents[_CONDITION_EVENT_ALL] = NULL; + return thrd_error; + } + cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL) + { + CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); + cond->mEvents[_CONDITION_EVENT_ONE] = NULL; + return thrd_error; + } + + return thrd_success; +#else + return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error; +#endif +} + +void cnd_destroy(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL) + { + CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); + } + if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL) + { + CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]); + } + DeleteCriticalSection(&cond->mWaitersCountLock); +#else + pthread_cond_destroy(cond); +#endif +} + +int cnd_signal(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + int haveWaiters; + + /* Are there any waiters? */ + EnterCriticalSection(&cond->mWaitersCountLock); + haveWaiters = (cond->mWaitersCount > 0); + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* If we have any waiting threads, send them a signal */ + if(haveWaiters) + { + if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0) + { + return thrd_error; + } + } + + return thrd_success; +#else + return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; +#endif +} + +int cnd_broadcast(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + int haveWaiters; + + /* Are there any waiters? */ + EnterCriticalSection(&cond->mWaitersCountLock); + haveWaiters = (cond->mWaitersCount > 0); + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* If we have any waiting threads, send them a signal */ + if(haveWaiters) + { + if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) + { + return thrd_error; + } + } + + return thrd_success; +#else + return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; +#endif +} + +#if defined(_TTHREAD_WIN32_) +static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout) +{ + int result, lastWaiter; + + /* Increment number of waiters */ + EnterCriticalSection(&cond->mWaitersCountLock); + ++ cond->mWaitersCount; + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* Release the mutex while waiting for the condition (will decrease + the number of waiters when done)... */ + mtx_unlock(mtx); + + /* Wait for either event to become signaled due to cnd_signal() or + cnd_broadcast() being called */ + result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout); + if (result == WAIT_TIMEOUT) + { + return thrd_timeout; + } + else if (result == (int)WAIT_FAILED) + { + return thrd_error; + } + + /* Check if we are the last waiter */ + EnterCriticalSection(&cond->mWaitersCountLock); + -- cond->mWaitersCount; + lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (cond->mWaitersCount == 0); + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* If we are the last waiter to be notified to stop waiting, reset the event */ + if (lastWaiter) + { + if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) + { + return thrd_error; + } + } + + /* Re-acquire the mutex */ + mtx_lock(mtx); + + return thrd_success; +} +#endif + +int cnd_wait(cnd_t *cond, mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + return _cnd_timedwait_win32(cond, mtx, INFINITE); +#else + return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error; +#endif +} + +int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts) +{ +#if defined(_TTHREAD_WIN32_) + struct timespec now; + if (clock_gettime(TIME_UTC, &now) == 0) + { + DWORD delta = (ts->tv_sec - now.tv_sec) * 1000 + + (ts->tv_nsec - now.tv_nsec + 500000) / 1000000; + return _cnd_timedwait_win32(cond, mtx, delta); + } + else + return thrd_error; +#else + int ret; + ret = pthread_cond_timedwait(cond, mtx, ts); + if (ret == ETIMEDOUT) + { + return thrd_timeout; + } + return ret == 0 ? thrd_success : thrd_error; +#endif +} + + +/** Information to pass to the new thread (what to run). */ +typedef struct { + thrd_start_t mFunction; /**< Pointer to the function to be executed. */ + void * mArg; /**< Function argument for the thread function. */ +} _thread_start_info; + +/* Thread wrapper function. */ +#if defined(_TTHREAD_WIN32_) +static unsigned WINAPI _thrd_wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +static void * _thrd_wrapper_function(void * aArg) +#endif +{ + thrd_start_t fun; + void *arg; + int res; +#if defined(_TTHREAD_POSIX_) + void *pres; +#endif + + /* Get thread startup information */ + _thread_start_info *ti = (_thread_start_info *) aArg; + fun = ti->mFunction; + arg = ti->mArg; + + /* The thread is responsible for freeing the startup information */ + free((void *)ti); + + /* Call the actual client thread function */ + res = fun(arg); + +#if defined(_TTHREAD_WIN32_) + return res; +#else + pres = malloc(sizeof(int)); + if (pres != NULL) + { + *(int*)pres = res; + } + return pres; +#endif +} + +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) +{ + /* Fill out the thread startup information (passed to the thread wrapper, + which will eventually free it) */ + _thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info)); + if (ti == NULL) + { + return thrd_nomem; + } + ti->mFunction = func; + ti->mArg = arg; + + /* Create the thread */ +#if defined(_TTHREAD_WIN32_) + *thr = (HANDLE)_beginthreadex(NULL, 0, _thrd_wrapper_function, (void *)ti, 0, NULL); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0) + { + *thr = 0; + } +#endif + + /* Did we fail to create the thread? */ + if(!*thr) + { + free(ti); + return thrd_error; + } + + return thrd_success; +} + +thrd_t thrd_current(void) +{ +#if defined(_TTHREAD_WIN32_) + return GetCurrentThread(); +#else + return pthread_self(); +#endif +} + +int thrd_detach(thrd_t thr) +{ + /* FIXME! */ + (void)thr; + return thrd_error; +} + +int thrd_equal(thrd_t thr0, thrd_t thr1) +{ +#if defined(_TTHREAD_WIN32_) + return thr0 == thr1; +#else + return pthread_equal(thr0, thr1); +#endif +} + +void thrd_exit(int res) +{ +#if defined(_TTHREAD_WIN32_) + ExitThread(res); +#else + void *pres = malloc(sizeof(int)); + if (pres != NULL) + { + *(int*)pres = res; + } + pthread_exit(pres); +#endif +} + +int thrd_join(thrd_t thr, int *res) +{ +#if defined(_TTHREAD_WIN32_) + if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED) + { + return thrd_error; + } + if (res != NULL) + { + DWORD dwRes; + GetExitCodeThread(thr, &dwRes); + *res = dwRes; + } +#elif defined(_TTHREAD_POSIX_) + void *pres; + int ires = 0; + if (pthread_join(thr, &pres) != 0) + { + return thrd_error; + } + if (pres != NULL) + { + ires = *(int*)pres; + free(pres); + } + if (res != NULL) + { + *res = ires; + } +#endif + return thrd_success; +} + +int thrd_sleep(const struct timespec *time_point, struct timespec *remaining) +{ + struct timespec now; +#if defined(_TTHREAD_WIN32_) + DWORD delta; +#else + long delta; +#endif + + /* Get the current time */ + if (clock_gettime(TIME_UTC, &now) != 0) + return -2; // FIXME: Some specific error code? + +#if defined(_TTHREAD_WIN32_) + /* Delta in milliseconds */ + delta = (time_point->tv_sec - now.tv_sec) * 1000 + + (time_point->tv_nsec - now.tv_nsec + 500000) / 1000000; + if (delta > 0) + { + Sleep(delta); + } +#else + /* Delta in microseconds */ + delta = (time_point->tv_sec - now.tv_sec) * 1000000L + + (time_point->tv_nsec - now.tv_nsec + 500L) / 1000L; + + /* On some systems, the usleep argument must be < 1000000 */ + while (delta > 999999L) + { + usleep(999999); + delta -= 999999L; + } + if (delta > 0L) + { + usleep((useconds_t)delta); + } +#endif + + /* We don't support waking up prematurely (yet) */ + if (remaining) + { + remaining->tv_sec = 0; + remaining->tv_nsec = 0; + } + return 0; +} + +void thrd_yield(void) +{ +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif +} + +int tss_create(tss_t *key, tss_dtor_t dtor) +{ +#if defined(_TTHREAD_WIN32_) + /* FIXME: The destructor function is not supported yet... */ + if (dtor != NULL) + { + return thrd_error; + } + *key = TlsAlloc(); + if (*key == TLS_OUT_OF_INDEXES) + { + return thrd_error; + } +#else + if (pthread_key_create(key, dtor) != 0) + { + return thrd_error; + } +#endif + return thrd_success; +} + +void tss_delete(tss_t key) +{ +#if defined(_TTHREAD_WIN32_) + TlsFree(key); +#else + pthread_key_delete(key); +#endif +} + +void *tss_get(tss_t key) +{ +#if defined(_TTHREAD_WIN32_) + return TlsGetValue(key); +#else + return pthread_getspecific(key); +#endif +} + +int tss_set(tss_t key, void *val) +{ +#if defined(_TTHREAD_WIN32_) + if (TlsSetValue(key, val) == 0) + { + return thrd_error; + } +#else + if (pthread_setspecific(key, val) != 0) + { + return thrd_error; + } +#endif + return thrd_success; +} + +#if defined(_TTHREAD_EMULATE_CLOCK_GETTIME_) +int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts) +{ +#if defined(_TTHREAD_WIN32_) + struct _timeb tb; + _ftime(&tb); + ts->tv_sec = (time_t)tb.time; + ts->tv_nsec = 1000000L * (long)tb.millitm; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + ts->tv_sec = (time_t)tv.tv_sec; + ts->tv_nsec = 1000L * (long)tv.tv_usec; +#endif + return 0; +} +#endif // _TTHREAD_EMULATE_CLOCK_GETTIME_ + diff --git a/tinycthread.h b/tinycthread.h new file mode 100644 index 0000000..1a9c805 --- /dev/null +++ b/tinycthread.h @@ -0,0 +1,437 @@ +/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYCTHREAD_H_ +#define _TINYCTHREAD_H_ + +/** +* @file +* @mainpage TinyCThread API Reference +* +* @section intro_sec Introduction +* TinyCThread is a minimal, portable implementation of basic threading +* classes for C. +* +* They closely mimic the functionality and naming of the C11 standard, and +* should be easily replaceable with the corresponding standard variants. +* +* @section port_sec Portability +* The Win32 variant uses the native Win32 API for implementing the thread +* classes, while for other systems, the POSIX threads API (pthread) is used. +* +* @section misc_sec Miscellaneous +* The following special keywords are available: #_Thread_local. +* +* For more detailed information, browse the different sections of this +* documentation. A good place to start is: +* tinycthread.h. +*/ + +/* Which platform are we on? */ +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */ +#if defined(_TTHREAD_POSIX_) + #undef _FEATURES_H + #if !defined(_GNU_SOURCE) + #define _GNU_SOURCE + #endif + #if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L + #endif + #if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500) + #undef _XOPEN_SOURCE + #define _XOPEN_SOURCE 500 + #endif +#endif + +/* Generic includes */ +#include <time.h> + +/* Platform specific includes */ +#if defined(_TTHREAD_POSIX_) + #include <pthread.h> +#elif defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include <windows.h> + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#endif + +/* Workaround for missing TIME_UTC: If time.h doesn't provide TIME_UTC, + it's quite likely that libc does not support it either. Hence, fall back to + the only other supported time specifier: CLOCK_REALTIME (and if that fails, + we're probably emulating clock_gettime anyway, so anything goes). */ +#ifndef TIME_UTC + #ifdef CLOCK_REALTIME + #define TIME_UTC CLOCK_REALTIME + #else + #define TIME_UTC 0 + #endif +#endif + +/* Workaround for missing clock_gettime (most Windows compilers, afaik) */ +#if defined(_TTHREAD_WIN32_) +#define _TTHREAD_EMULATE_CLOCK_GETTIME_ +/* Emulate struct timespec */ +struct _ttherad_timespec { + time_t tv_sec; + long tv_nsec; +}; +#define timespec _ttherad_timespec + +/* Emulate clockid_t */ +typedef int _tthread_clockid_t; +#define clockid_t _tthread_clockid_t + +/* Emulate clock_gettime */ +int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts); +#define clock_gettime _tthread_clock_gettime +#endif + + +/** TinyCThread version (major number). */ +#define TINYCTHREAD_VERSION_MAJOR 1 +/** TinyCThread version (minor number). */ +#define TINYCTHREAD_VERSION_MINOR 1 +/** TinyCThread version (full version). */ +#define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR) + +/** +* @def _Thread_local +* Thread local storage keyword. +* A variable that is declared with the @c _Thread_local keyword makes the +* value of the variable local to each thread (known as thread-local storage, +* or TLS). Example usage: +* @code +* // This variable is local to each thread. +* _Thread_local int variable; +* @endcode +* @note The @c _Thread_local keyword is a macro that maps to the corresponding +* compiler directive (e.g. @c __declspec(thread)). +* @note This directive is currently not supported on Mac OS X (it will give +* a compiler error), since compile-time TLS is not supported in the Mac OS X +* executable format. Also, some older versions of MinGW (before GCC 4.x) do +* not support this directive. +* @hideinitializer +*/ + +/* FIXME: Check for a PROPER value of __STDC_VERSION__ to know if we have C11 */ +#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define _Thread_local __thread + #else + #define _Thread_local __declspec(thread) + #endif +#endif + +/* Macros */ +#define TSS_DTOR_ITERATIONS 0 + +/* Function return values */ +#define thrd_error 0 /**< The requested operation failed */ +#define thrd_success 1 /**< The requested operation succeeded */ +#define thrd_timeout 2 /**< The time specified in the call was reached without acquiring the requested resource */ +#define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */ +#define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */ + +/* Mutex types */ +#define mtx_plain 1 +#define mtx_timed 2 +#define mtx_try 4 +#define mtx_recursive 8 + +/* Mutex */ +#if defined(_TTHREAD_WIN32_) +typedef struct { + CRITICAL_SECTION mHandle; /* Critical section handle */ + int mAlreadyLocked; /* TRUE if the mutex is already locked */ + int mRecursive; /* TRUE if the mutex is recursive */ +} mtx_t; +#else +typedef pthread_mutex_t mtx_t; +#endif + +/** Create a mutex object. +* @param mtx A mutex object. +* @param type Bit-mask that must have one of the following six values: +* @li @c mtx_plain for a simple non-recursive mutex +* @li @c mtx_timed for a non-recursive mutex that supports timeout +* @li @c mtx_try for a non-recursive mutex that supports test and return +* @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive) +* @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive) +* @li @c mtx_try | @c mtx_recursive (same as @c mtx_try, but recursive) +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int mtx_init(mtx_t *mtx, int type); + +/** Release any resources used by the given mutex. +* @param mtx A mutex object. +*/ +void mtx_destroy(mtx_t *mtx); + +/** Lock the given mutex. +* Blocks until the given mutex can be locked. If the mutex is non-recursive, and +* the calling thread already has a lock on the mutex, this call will block +* forever. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int mtx_lock(mtx_t *mtx); + +/** NOT YET IMPLEMENTED. +*/ +int mtx_timedlock(mtx_t *mtx, const struct timespec *ts); + +/** Try to lock the given mutex. +* The specified mutex shall support either test and return or timeout. If the +* mutex is already locked, the function returns without blocking. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_busy if the resource +* requested is already in use, or @ref thrd_error if the request could not be +* honored. +*/ +int mtx_trylock(mtx_t *mtx); + +/** Unlock the given mutex. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int mtx_unlock(mtx_t *mtx); + +/* Condition variable */ +#if defined(_TTHREAD_WIN32_) +typedef struct { + HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */ + unsigned int mWaitersCount; /* Count of the number of waiters. */ + CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */ +} cnd_t; +#else +typedef pthread_cond_t cnd_t; +#endif + +/** Create a condition variable object. +* @param cond A condition variable object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_init(cnd_t *cond); + +/** Release any resources used by the given condition variable. +* @param cond A condition variable object. +*/ +void cnd_destroy(cnd_t *cond); + +/** Signal a condition variable. +* Unblocks one of the threads that are blocked on the given condition variable +* at the time of the call. If no threads are blocked on the condition variable +* at the time of the call, the function does nothing and return success. +* @param cond A condition variable object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_signal(cnd_t *cond); + +/** Broadcast a condition variable. +* Unblocks all of the threads that are blocked on the given condition variable +* at the time of the call. If no threads are blocked on the condition variable +* at the time of the call, the function does nothing and return success. +* @param cond A condition variable object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_broadcast(cnd_t *cond); + +/** Wait for a condition variable to become signaled. +* The function atomically unlocks the given mutex and endeavors to block until +* the given condition variable is signaled by a call to cnd_signal or to +* cnd_broadcast. When the calling thread becomes unblocked it locks the mutex +* before it returns. +* @param cond A condition variable object. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_wait(cnd_t *cond, mtx_t *mtx); + +/** Wait for a condition variable to become signaled. +* The function atomically unlocks the given mutex and endeavors to block until +* the given condition variable is signaled by a call to cnd_signal or to +* cnd_broadcast, or until after the specified time. When the calling thread +* becomes unblocked it locks the mutex before it returns. +* @param cond A condition variable object. +* @param mtx A mutex object. +* @param xt A point in time at which the request will time out (absolute time). +* @return @ref thrd_success upon success, or @ref thrd_timeout if the time +* specified in the call was reached without acquiring the requested resource, or +* @ref thrd_error if the request could not be honored. +*/ +int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts); + +/* Thread */ +#if defined(_TTHREAD_WIN32_) +typedef HANDLE thrd_t; +#else +typedef pthread_t thrd_t; +#endif + +/** Thread start function. +* Any thread that is started with the @ref thrd_create() function must be +* started through a function of this type. +* @param arg The thread argument (the @c arg argument of the corresponding +* @ref thrd_create() call). +* @return The thread return value, which can be obtained by another thread +* by using the @ref thrd_join() function. +*/ +typedef int (*thrd_start_t)(void *arg); + +/** Create a new thread. +* @param thr Identifier of the newly created thread. +* @param func A function pointer to the function that will be executed in +* the new thread. +* @param arg An argument to the thread function. +* @return @ref thrd_success on success, or @ref thrd_nomem if no memory could +* be allocated for the thread requested, or @ref thrd_error if the request +* could not be honored. +* @note A thread’s identifier may be reused for a different thread once the +* original thread has exited and either been detached or joined to another +* thread. +*/ +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); + +/** Identify the calling thread. +* @return The identifier of the calling thread. +*/ +thrd_t thrd_current(void); + +/** NOT YET IMPLEMENTED. +*/ +int thrd_detach(thrd_t thr); + +/** Compare two thread identifiers. +* The function determines if two thread identifiers refer to the same thread. +* @return Zero if the two thread identifiers refer to different threads. +* Otherwise a nonzero value is returned. +*/ +int thrd_equal(thrd_t thr0, thrd_t thr1); + +/** Terminate execution of the calling thread. +* @param res Result code of the calling thread. +*/ +void thrd_exit(int res); + +/** Wait for a thread to terminate. +* The function joins the given thread with the current thread by blocking +* until the other thread has terminated. +* @param thr The thread to join with. +* @param res If this pointer is not NULL, the function will store the result +* code of the given thread in the integer pointed to by @c res. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int thrd_join(thrd_t thr, int *res); + +/** Put the calling thread to sleep. +* Suspend execution of the calling thread. +* @param time_point A point in time at which the thread will resume (absolute time). +* @param remaining If non-NULL, this parameter will hold the remaining time until +* time_point upon return. This will typically be zero, but if +* the thread was woken up by a signal that is not ignored before +* time_point was reached @c remaining will hold a positive +* time. +* @return 0 (zero) on successful sleep, or -1 if an interrupt occurred. +*/ +int thrd_sleep(const struct timespec *time_point, struct timespec *remaining); + +/** Yield execution to another thread. +* Permit other threads to run, even if the current thread would ordinarily +* continue to run. +*/ +void thrd_yield(void); + +/* Thread local storage */ +#if defined(_TTHREAD_WIN32_) +typedef DWORD tss_t; +#else +typedef pthread_key_t tss_t; +#endif + +/** Destructor function for a thread-specific storage. +* @param val The value of the destructed thread-specific storage. +*/ +typedef void (*tss_dtor_t)(void *val); + +/** Create a thread-specific storage. +* @param key The unique key identifier that will be set if the function is +* successful. +* @param dtor Destructor function. This can be NULL. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +* @note The destructor function is not supported under Windows. If @c dtor is +* not NULL when calling this function under Windows, the function will fail +* and return @ref thrd_error. +*/ +int tss_create(tss_t *key, tss_dtor_t dtor); + +/** Delete a thread-specific storage. +* The function releases any resources used by the given thread-specific +* storage. +* @param key The key that shall be deleted. +*/ +void tss_delete(tss_t key); + +/** Get the value for a thread-specific storage. +* @param key The thread-specific storage identifier. +* @return The value for the current thread held in the given thread-specific +* storage. +*/ +void *tss_get(tss_t key); + +/** Set the value for a thread-specific storage. +* @param key The thread-specific storage identifier. +* @param val The value of the thread-specific storage to set for the current +* thread. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int tss_set(tss_t key, void *val); + + +#endif /* _TINYTHREAD_H_ */ + |
