aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2020-03-20 21:50:33 +0100
committercbdev <cb@cbcdn.com>2020-03-20 21:50:33 +0100
commit2d66d5cee9bf3ed5779f65d8a99b40ee5181bf30 (patch)
tree9fbf00807fd06a9a48871d05de26625e6c1fb9fd
parent9718e10c7f4151cea895f515c785c14e0021d967 (diff)
downloadmidimonster-2d66d5cee9bf3ed5779f65d8a99b40ee5181bf30.tar.gz
midimonster-2d66d5cee9bf3ed5779f65d8a99b40ee5181bf30.tar.bz2
midimonster-2d66d5cee9bf3ed5779f65d8a99b40ee5181bf30.zip
Implement Lua threading
-rw-r--r--backends/lua.c114
-rw-r--r--backends/lua.h7
-rw-r--r--backends/lua.md30
3 files changed, 144 insertions, 7 deletions
diff --git a/backends/lua.c b/backends/lua.c
index 498a037..b66a27a 100644
--- a/backends/lua.c
+++ b/backends/lua.c
@@ -10,6 +10,7 @@
#define LUA_REGISTRY_KEY "_midimonster_lua_instance"
#define LUA_REGISTRY_CURRENT_CHANNEL "_midimonster_lua_channel"
+#define LUA_REGISTRY_CURRENT_THREAD "_midimonster_lua_thread"
static size_t timers = 0;
static lua_timer* timer = NULL;
@@ -20,6 +21,9 @@ static int timer_fd = -1;
static uint64_t last_timestamp;
#endif
+static size_t threads = 0;
+static lua_thread* thread = NULL;
+
MM_PLUGIN_API int init(){
backend lua = {
#ifndef MMBACKEND_LUA_TIMERFD
@@ -86,6 +90,12 @@ static int lua_update_timerfd(){
interval = timer[n].interval;
}
}
+
+ for(n = 0; n < threads; n++){
+ if(thread[n].timeout && (!interval || thread[n].timeout < interval)){
+ interval = thread[n].timeout;
+ }
+ }
DBGPF("Recalculating timers, minimum is %" PRIu64, interval);
//calculate gcd of all timers if any are active
@@ -100,7 +110,8 @@ static int lua_update_timerfd(){
gcd = residual;
}
//since we round everything, 10 is the lowest interval we get
- if(interval == 10){
+ if(interval <= 10){
+ interval = 10;
break;
}
}
@@ -126,6 +137,89 @@ static int lua_update_timerfd(){
return 0;
}
+static void lua_thread_resume(size_t current_thread){
+ //push coroutine reference
+ lua_pushstring(thread[current_thread].thread, LUA_REGISTRY_CURRENT_THREAD);
+ lua_pushnumber(thread[current_thread].thread, current_thread);
+ lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX);
+
+ //call thread main
+ DBGPF("Resuming thread %" PRIsize_t " on %s", current_thread, thread[current_thread].instance->name);
+ if(lua_resume(thread[current_thread].thread, NULL, 0) != LUA_YIELD){
+ DBGPF("Thread %" PRIsize_t " on %s terminated", current_thread, thread[current_thread].instance->name);
+ thread[current_thread].timeout = 0;
+ }
+
+ //remove coroutine reference
+ lua_pushstring(thread[current_thread].thread, LUA_REGISTRY_CURRENT_THREAD);
+ lua_pushnil(thread[current_thread].thread);
+ lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX);
+}
+
+static int lua_callback_thread(lua_State* interpreter){
+ instance* inst = NULL;
+ size_t u = threads;
+ if(lua_gettop(interpreter) != 1){
+ LOGPF("Thread function called with %d arguments, expected function", lua_gettop(interpreter));
+ return 0;
+ }
+
+ luaL_checktype(interpreter, 1, LUA_TFUNCTION);
+
+ //get instance pointer from registry
+ lua_pushstring(interpreter, LUA_REGISTRY_KEY);
+ lua_gettable(interpreter, LUA_REGISTRYINDEX);
+ inst = (instance*) lua_touserdata(interpreter, -1);
+
+ //make space for a new thread
+ thread = realloc(thread, (threads + 1) * sizeof(lua_thread));
+ if(!thread){
+ threads = 0;
+ LOG("Failed to allocate memory");
+ return 0;
+ }
+ threads++;
+
+ thread[u].thread = lua_newthread(interpreter);
+ thread[u].instance = inst;
+ thread[u].timeout = 0;
+ thread[u].reference = luaL_ref(interpreter, LUA_REGISTRYINDEX);
+
+ DBGPF("Registered thread %" PRIsize_t " on %s", threads, inst->name);
+
+ //push thread main
+ luaL_checktype(interpreter, 1, LUA_TFUNCTION);
+ lua_pushvalue(interpreter, 1);
+ lua_xmove(interpreter, thread[u].thread, 1);
+
+ lua_thread_resume(u);
+ lua_update_timerfd();
+ return 0;
+}
+
+static int lua_callback_sleep(lua_State* interpreter){
+ uint64_t timeout = 0;
+ size_t current_thread = threads;
+ if(lua_gettop(interpreter) != 1){
+ LOGPF("Sleep function called with %d arguments, expected number", lua_gettop(interpreter));
+ return 0;
+ }
+
+ timeout = luaL_checkinteger(interpreter, 1);
+
+ lua_pushstring(interpreter, LUA_REGISTRY_CURRENT_THREAD);
+ lua_gettable(interpreter, LUA_REGISTRYINDEX);
+
+ current_thread = luaL_checkinteger(interpreter, -1);
+
+ if(current_thread < threads){
+ DBGPF("Yielding for %" PRIu64 "msec on thread %" PRIsize_t, timeout, current_thread);
+ thread[current_thread].timeout = timeout;
+ lua_yield(interpreter, 0);
+ }
+ return 0;
+}
+
static int lua_callback_output(lua_State* interpreter){
size_t n = 0;
channel_value val;
@@ -341,6 +435,8 @@ static int lua_instance(instance* inst){
lua_register(data->interpreter, "output_value", lua_callback_output_value);
lua_register(data->interpreter, "input_channel", lua_callback_input_channel);
lua_register(data->interpreter, "timestamp", lua_callback_timestamp);
+ lua_register(data->interpreter, "thread", lua_callback_thread);
+ lua_register(data->interpreter, "sleep", lua_callback_sleep);
//store instance pointer to the lua state
lua_pushstring(data->interpreter, LUA_REGISTRY_KEY);
@@ -454,6 +550,17 @@ static int lua_handle(size_t num, managed_fd* fds){
}
}
}
+
+ //check for threads to wake up
+ for(n = 0; n < threads; n++){
+ if(thread[n].timeout && delta >= thread[n].timeout){
+ lua_thread_resume(n);
+ lua_update_timerfd();
+ }
+ else if(thread[n].timeout){
+ thread[n].timeout -= delta;
+ }
+ }
return 0;
}
@@ -468,6 +575,8 @@ static int lua_start(size_t n, instance** inst){
//exclude reserved names
if(!data->default_handler
&& strcmp(data->channel_name[p], "output")
+ && strcmp(data->channel_name[p], "thread")
+ && strcmp(data->channel_name[p], "sleep")
&& strcmp(data->channel_name[p], "input_value")
&& strcmp(data->channel_name[p], "output_value")
&& strcmp(data->channel_name[p], "input_channel")
@@ -526,6 +635,9 @@ static int lua_shutdown(size_t n, instance** inst){
free(timer);
timer = NULL;
timers = 0;
+ free(thread);
+ thread = NULL;
+ threads = 0;
#ifdef MMBACKEND_LUA_TIMERFD
close(timer_fd);
timer_fd = -1;
diff --git a/backends/lua.h b/backends/lua.h
index 743f978..d8e720a 100644
--- a/backends/lua.h
+++ b/backends/lua.h
@@ -39,3 +39,10 @@ typedef struct /*_lua_interval_callback*/ {
lua_State* interpreter;
int reference;
} lua_timer;
+
+typedef struct /*_lua_coroutine*/ {
+ instance* instance;
+ lua_State* thread;
+ int reference;
+ uint64_t timeout;
+} lua_thread;
diff --git a/backends/lua.md b/backends/lua.md
index 96e53c8..05509b6 100644
--- a/backends/lua.md
+++ b/backends/lua.md
@@ -19,24 +19,41 @@ The following functions are provided within the Lua interpreter for interaction
| `output_value(string)` | `output_value("bar")` | Get the last output value on a channel |
| `input_channel()` | `print(input_channel())` | Returns the name of the input channel whose handler function is currently running or `nil` if in an `interval`'ed function (or the initial parse step) |
| `timestamp()` | `print(timestamp())` | Returns the core timestamp for this iteration with millisecond resolution. This is not a performance timer, but intended for timeouting, etc |
+| `thread(function)` | `thread(run_show)` | Run a function as a Lua thread (see below) |
+| `sleep(number)` | `sleep(100)` | Suspend current thread for time specified in milliseconds |
Example script:
-```
+```lua
function bar(value)
- output("foo", value / 2)
+ output("foo", value / 2);
end
step = 0
function toggle()
- output("bar", step * 1.0)
+ output("bar", step * 1.0);
step = (step + 1) % 2;
end
+function run_show()
+ while(true) do
+ sleep(1000);
+ output("narf", 0);
+ sleep(1000);
+ output("narf", 1.0);
+ end
+end
+
interval(toggle, 1000)
+thread(run_show)
```
Input values range between 0.0 and 1.0, output values are clamped to the same range.
+Threads are implemented as Lua coroutines, not operating system threads. This means that
+cooperative multithreading is required, which can be achieved by calling the `sleep(number)`
+function from within a running thread. Calling that function from any other context is
+not supported.
+
#### Global configuration
The `lua` backend does not take any global configuration.
@@ -61,9 +78,10 @@ lua1.foo > lua2.bar
#### Known bugs / problems
-Using any of the interface functions (`output`, `interval`, `input_value`, `output_value`, `input_channel`,
-`timestamp`) as an input channel name to a Lua instance will not call any handler functions.
-Using these names as arguments to the output and value interface functions works as intended.
+Using any of the interface functions (`output`, `interval`, etc.) as an input channel name to a
+Lua instance will not call any handler functions. Using these names as arguments to the output and
+value interface functions works as intended. When using a default handler, the default handler will
+be called.
Output values will not trigger corresponding input event handlers unless the channel is mapped
back in the MIDIMonster configuration. This is intentional.